Update to 1.21.6

This commit is contained in:
2025-06-29 20:43:51 +02:00
parent 16fab96836
commit 7b540521dc
16 changed files with 389 additions and 670 deletions

View File

@@ -73,6 +73,7 @@ Code inspired by Inferis!
## Client Side ## Client Side
- [x] Fixed bug with too high zoom step values - [x] Fixed bug with too high zoom step values
- [x] Rewrote config screen using YACL
## Server Side ## Server Side
- [x] Telekinesis - [x] Telekinesis
@@ -83,7 +84,6 @@ Code inspired by Inferis!
# To-do # To-do
## General ## General
- Rework config system
- Store server settings in world folder for better singleplayer use - Store server settings in world folder for better singleplayer use
## Client side ## Client side

View File

@@ -15,6 +15,10 @@ repositories {
name = "Terraformers" name = "Terraformers"
url = "https://maven.terraformersmc.com/" url = "https://maven.terraformersmc.com/"
} }
maven {
name = "Xander Maven"
url = "https://maven.isxander.dev/releases"
}
} }
loom { loom {
@@ -43,6 +47,7 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway. // Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}"
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}") modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
} }

View File

@@ -4,16 +4,17 @@ org.gradle.parallel=true
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.21.5 minecraft_version=1.21.6
yarn_mappings=1.21.5+build.1 yarn_mappings=1.21.6+build.1
loader_version=0.16.10 loader_version=0.16.14
# Mod Properties # Mod Properties
mod_version=1.4.1 mod_version=1.4.2
maven_group=wtf.hak.survivalfabric maven_group=wtf.hak.survivalfabric
archives_base_name=survivalfabric archives_base_name=survivalfabric
# Dependencies # Dependencies
fabric_version=0.119.5+1.21.5 fabric_version=0.127.0+1.21.6
yacl_version=3.7.1+1.21.6-fabric
modmenu_version=14.0.0-rc.2 modmenu_version=15.0.0-beta.1

View File

@@ -32,9 +32,7 @@ public class Zoom {
MinecraftClient.getInstance().options.smoothCameraEnabled = true; MinecraftClient.getInstance().options.smoothCameraEnabled = true;
} }
SHOULD_ZOOM = true; SHOULD_ZOOM = true;
client.gameRenderer.setRenderHand(false);
} else if (!ZOOM_BIND.isPressed() && SHOULD_ZOOM) { } else if (!ZOOM_BIND.isPressed() && SHOULD_ZOOM) {
client.gameRenderer.setRenderHand(true);
SHOULD_ZOOM = false; SHOULD_ZOOM = false;
ZOOM_STEP = 0; ZOOM_STEP = 0;
if(getConfig().smoothCamera) { if(getConfig().smoothCamera) {

View File

@@ -1,47 +0,0 @@
package wtf.hak.survivalfabric.mixin.client;
import net.minecraft.block.enums.CameraSubmersionType;
import net.minecraft.client.render.BackgroundRenderer;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Fog;
import net.minecraft.client.render.FogShape;
import net.minecraft.world.World;
import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
@Mixin(value = BackgroundRenderer.class, priority = 910)
public class BackgroundRendererMixin {
@Unique
private static final Fog EMPTY_FOG = new Fog(-8.0f, 1_000_000.0F, FogShape.CYLINDER, 0, 0, 0, 0);
@Inject(method = "applyFog", at = @At("RETURN"), cancellable = true)
private static void applyFog(Camera camera, BackgroundRenderer.FogType fogType, Vector4f color, float viewDistance, boolean thickenFog, float tickProgress, CallbackInfoReturnable<Fog> cir) {
CameraSubmersionType submersion = camera.getSubmersionType();
boolean renderFog = true;
switch (submersion) {
case NONE -> {
World world = camera.getFocusedEntity().getWorld();
if ((world.getRegistryKey() == World.OVERWORLD && !getConfig().renderOverworldFog)
|| (world.getRegistryKey() == World.NETHER && !getConfig().renderNetherFog)
|| (world.getRegistryKey() == World.END && !getConfig().renderEndFog)) {
renderFog = false;
}
}
case WATER -> renderFog = getConfig().renderWaterFog;
case LAVA -> renderFog = getConfig().renderLavaFog;
case POWDER_SNOW -> renderFog = getConfig().renderSnowFog;
}
if (!renderFog) {
cir.setReturnValue(EMPTY_FOG);
}
}
}

View File

@@ -0,0 +1,80 @@
package wtf.hak.survivalfabric.mixin.client;
import net.minecraft.client.render.RenderTickCounter;
import net.minecraft.client.render.fog.*;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
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;
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
public class FogMixins {
@Mixin(DimensionOrBossFogModifier.class)
static class DimensionOrBossFogModifierMixin {
@Inject(at = @At("TAIL"), method = "applyStartEndModifier")
public void disableDimensionFog(FogData data, Entity cameraEntity, BlockPos cameraPos, ClientWorld world, float viewDistance, RenderTickCounter tickCounter, CallbackInfo ci) {
boolean shouldDisable = false;
if (world.getRegistryKey() == World.NETHER && !getConfig().renderNetherFog) {
shouldDisable = true;
} else if (world.getRegistryKey() == World.END && !getConfig().renderEndFog) {
shouldDisable = true;
} else if (world.getRegistryKey() == World.OVERWORLD && !getConfig().renderOverworldFog) {
shouldDisable = true;
}
if (shouldDisable) {
data.environmentalStart = Float.MAX_VALUE;
data.environmentalEnd = Float.MAX_VALUE;
data.skyEnd = Float.MAX_VALUE;
data.cloudEnd = Float.MAX_VALUE;
}
}
}
@Mixin(WaterFogModifier.class)
static class WaterFogModifierMixin {
@Inject(at = @At("TAIL"), method = "applyStartEndModifier")
public void disableWaterFog(FogData data, Entity cameraEntity, BlockPos cameraPos, ClientWorld world, float viewDistance, RenderTickCounter tickCounter, CallbackInfo ci) {
if (!getConfig().renderWaterFog) {
data.environmentalStart = Float.MAX_VALUE;
data.environmentalEnd = Float.MAX_VALUE;
data.skyEnd = Float.MAX_VALUE;
data.cloudEnd = Float.MAX_VALUE;
}
}
}
@Mixin(LavaFogModifier.class)
static class LavaFogModifierMixin {
@Inject(at = @At("TAIL"), method = "applyStartEndModifier")
public void disableLavaFog(FogData data, Entity cameraEntity, BlockPos cameraPos, ClientWorld world, float viewDistance, RenderTickCounter tickCounter, CallbackInfo ci) {
if (!getConfig().renderLavaFog) {
data.environmentalStart = Float.MAX_VALUE;
data.environmentalEnd = Float.MAX_VALUE;
data.skyEnd = Float.MAX_VALUE;
data.cloudEnd = Float.MAX_VALUE;
}
}
}
@Mixin(PowderSnowFogModifier.class)
static class PowderSnowFogModifierMixin {
@Inject(at = @At("TAIL"), method = "applyStartEndModifier")
public void disablePowderSnowFog(FogData data, Entity cameraEntity, BlockPos cameraPos, ClientWorld world, float viewDistance, RenderTickCounter tickCounter, CallbackInfo ci) {
if (!getConfig().renderSnowFog) {
data.environmentalStart = Float.MAX_VALUE;
data.environmentalEnd = Float.MAX_VALUE;
data.skyEnd = Float.MAX_VALUE;
data.cloudEnd = Float.MAX_VALUE;
}
}
}
}

View File

@@ -1,8 +1,14 @@
package wtf.hak.survivalfabric.mixin.client; package wtf.hak.survivalfabric.mixin.client;
import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import net.fabricmc.fabric.api.client.rendering.v1.hud.HudElement;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.GameRenderer; import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.RenderTickCounter;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import wtf.hak.survivalfabric.features.Zoom; import wtf.hak.survivalfabric.features.Zoom;
@@ -18,4 +24,13 @@ public class GameRendererMixin {
return Zoom.isZooming() ? Math.clamp(Zoom.getZoomFov(), 1, 110) : fov; return Zoom.isZooming() ? Math.clamp(Zoom.getZoomFov(), 1, 110) : fov;
} }
/**
* Make sure hand only gets rendered when NOT zooming.
*/
@WrapMethod(method = "renderHand")
public void render(float tickProgress, boolean sleeping, Matrix4f positionMatrix, Operation<Void> original) {
if(!Zoom.isZooming())
original.call(tickProgress, sleeping, positionMatrix);
}
} }

View File

@@ -1,599 +0,0 @@
package wtf.hak.survivalfabric.modmenu;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.TextFieldWidget;
import net.minecraft.text.Text;
import net.minecraft.util.math.MathHelper;
import wtf.hak.survivalfabric.config.client.ClientConfig;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class ConfigScreen extends Screen {
private static final int OPTION_HEIGHT = 25;
private static final int SCROLL_BAR_WIDTH = 6;
private static final int TOP_PADDING = 40;
private static final int BOTTOM_PADDING = 35;
private static final int SIDE_PADDING = 20;
private final Screen parent;
private final List<ConfigOption<?>> options = new ArrayList<>();
private TextFieldWidget activeTextField = null;
private float scrollPosition = 0.0F;
private boolean scrolling = false;
private int contentHeight = 0;
public ConfigScreen(Screen parent) {
super(Text.literal("Survival Fabric - Client Config"));
this.parent = parent;
}
@Override
protected void init() {
options.clear();
int listWidth = this.width - (SIDE_PADDING * 2);
int listHeight = this.height - TOP_PADDING - BOTTOM_PADDING;
int listLeft = SIDE_PADDING;
int listTop = TOP_PADDING;
int index = 0;
for (Field field : ClientConfig.class.getFields()) {
try {
Class<?> type = field.getType();
String name = formatFieldName(field.getName());
ConfigOption<?> option = null;
if (type == boolean.class) {
boolean value = field.getBoolean(ClientConfigManager.getConfig());
option = new BooleanConfigOption(
name,
field,
value,
listLeft,
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
listWidth
);
} else if (type == int.class) {
int value = field.getInt(ClientConfigManager.getConfig());
option = new IntegerConfigOption(
name,
field,
value,
listLeft,
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
listWidth
);
} else if (type == float.class) {
float value = field.getFloat(ClientConfigManager.getConfig());
option = new FloatConfigOption(
name,
field,
value,
listLeft,
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
listWidth
);
} else if (type == String.class) {
String value = (String) field.get(ClientConfigManager.getConfig());
option = new StringConfigOption(
name,
field,
value,
listLeft,
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
listWidth
);
}
if (option != null) {
options.add(option);
index++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
for (ConfigOption<?> option : options) {
if (option instanceof NumericConfigOption) {
((NumericConfigOption<?>) option).createTextField(this.client);
}
}
contentHeight = options.size() * OPTION_HEIGHT;
this.addDrawableChild(ButtonWidget.builder(
Text.translatable("gui.done"),
button -> this.client.setScreen(parent)
).dimensions(this.width / 2 - 100, this.height - 27, 200, 20).build());
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
this.renderBackground(context, mouseX, mouseY, delta);
int titleX = (this.width / 2) - (this.textRenderer.getWidth(this.title) / 2);
context.drawText(this.textRenderer, this.title, titleX, 15, 0xFFFFFF, true);
int listWidth = this.width - (SIDE_PADDING * 2);
int listHeight = this.height - TOP_PADDING - BOTTOM_PADDING;
int listLeft = SIDE_PADDING;
int listTop = TOP_PADDING;
context.drawBorder(listLeft, listTop, listLeft + listWidth, listTop + listHeight, 0x00000000);
context.enableScissor(
listLeft,
listTop,
listLeft + listWidth,
listTop + listHeight
);
for (int i = 0; i < options.size(); i++) {
ConfigOption<?> option = options.get(i);
option.y = listTop + (i * OPTION_HEIGHT) - (int) scrollPosition;
if (option instanceof NumericConfigOption) {
((NumericConfigOption<?>) option).updateTextFieldPosition();
}
if (option.y < listTop + listHeight && option.y + OPTION_HEIGHT > listTop) {
option.render(context, mouseX, mouseY, delta);
}
}
context.disableScissor();
if (contentHeight > listHeight) {
int scrollBarHeight = Math.max(20, (int) ((float) listHeight / (float) contentHeight * listHeight));
int scrollBarY = listTop + (int) ((scrollPosition / (contentHeight - listHeight)) * (listHeight - scrollBarHeight));
context.fill(
listLeft + listWidth + 2,
listTop,
listLeft + listWidth + 2 + SCROLL_BAR_WIDTH,
listTop + listHeight,
0xFF404040
);
context.fill(
listLeft + listWidth + 2,
scrollBarY,
listLeft + listWidth + 2 + SCROLL_BAR_WIDTH,
scrollBarY + scrollBarHeight,
scrolling ? 0xFFAAAAAA : 0xFF808080
);
}
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
int listWidth = this.width - (SIDE_PADDING * 2);
int listHeight = this.height - TOP_PADDING - BOTTOM_PADDING;
int listLeft = SIDE_PADDING;
int listTop = TOP_PADDING;
boolean handledByTextField = false;
activeTextField = null;
if (mouseX >= listLeft &&
mouseX <= listLeft + listWidth &&
mouseY >= listTop &&
mouseY <= listTop + listHeight) {
for (ConfigOption<?> option : options) {
if (option instanceof NumericConfigOption<?> numOption &&
option.y >= listTop &&
option.y + OPTION_HEIGHT <= listTop + listHeight) {
TextFieldWidget textField = numOption.getTextField();
if (textField.isMouseOver(mouseX, mouseY)) {
textField.setFocused(true);
activeTextField = textField;
handledByTextField = true;
for (ConfigOption<?> otherOption : options) {
if (otherOption instanceof NumericConfigOption && otherOption != option) {
((NumericConfigOption<?>) otherOption).getTextField().setFocused(false);
}
}
return true;
} else {
textField.setFocused(false);
}
}
}
}
if (contentHeight > listHeight &&
mouseX >= listLeft + listWidth + 2 &&
mouseX <= listLeft + listWidth + 2 + SCROLL_BAR_WIDTH &&
mouseY >= listTop &&
mouseY <= listTop + listHeight) {
scrolling = true;
return true;
}
// Check if clicked on an option
if (mouseX >= listLeft &&
mouseX <= listLeft + listWidth &&
mouseY >= listTop &&
mouseY <= listTop + listHeight) {
for (ConfigOption<?> option : options) {
if (option.isMouseOver(mouseX, mouseY) && option.y >= listTop && option.y + OPTION_HEIGHT <= listTop + listHeight) {
option.onClick(mouseX, mouseY);
return true;
}
}
}
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (activeTextField != null && activeTextField.isFocused()) {
return activeTextField.keyPressed(keyCode, scanCode, modifiers);
}
return super.keyPressed(keyCode, scanCode, modifiers);
}
@Override
public boolean charTyped(char chr, int modifiers) {
if (activeTextField != null && activeTextField.isFocused()) {
return activeTextField.charTyped(chr, modifiers);
}
return super.charTyped(chr, modifiers);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
scrolling = false;
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
int listHeight = this.height - TOP_PADDING - BOTTOM_PADDING;
int listTop = TOP_PADDING;
if (scrolling && contentHeight > listHeight) {
float scrollAmount = (float) deltaY / (listHeight - Math.max(20, (int) ((float) listHeight / (float) contentHeight * listHeight)));
float maxScroll = contentHeight - listHeight;
scrollPosition = MathHelper.clamp(scrollPosition + scrollAmount * maxScroll, 0.0F, maxScroll);
return true;
}
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
int listWidth = this.width - (SIDE_PADDING * 2);
int listHeight = this.height - TOP_PADDING - BOTTOM_PADDING;
int listLeft = SIDE_PADDING;
int listTop = TOP_PADDING;
if (mouseX >= listLeft &&
mouseX <= listLeft + listWidth &&
mouseY >= listTop &&
mouseY <= listTop + listHeight &&
contentHeight > listHeight) {
float maxScroll = contentHeight - listHeight;
scrollPosition = MathHelper.clamp(scrollPosition - (float) verticalAmount * 10, 0.0F, maxScroll);
return true;
}
return super.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount);
}
private String formatFieldName(String rawName) {
StringBuilder result = new StringBuilder();
char[] chars = rawName.toCharArray();
result.append(Character.toUpperCase(chars[0]));
for (int i = 1; i < chars.length; i++) {
if (Character.isUpperCase(chars[i])) {
result.append(' ');
}
result.append(chars[i]);
}
return result.toString();
}
/**
* Base class for all config options
*/
private abstract class ConfigOption<T> {
protected final String name;
protected final Field field;
protected final int width;
protected T value;
protected int x;
protected int y;
public ConfigOption(String name, Field field, T initialValue, int x, int y, int width) {
this.name = name;
this.field = field;
this.value = initialValue;
this.x = x;
this.y = y;
this.width = width;
}
public abstract void render(DrawContext context, int mouseX, int mouseY, float delta);
public abstract void onClick(double mouseX, double mouseY);
public boolean isMouseOver(double mouseX, double mouseY) {
return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + OPTION_HEIGHT;
}
protected abstract void saveValue();
}
/**
* Implementation for boolean config options
*/
private class BooleanConfigOption extends ConfigOption<Boolean> {
private static final int BUTTON_WIDTH = 40;
private static final int BUTTON_HEIGHT = 20;
public BooleanConfigOption(String name, Field field, Boolean initialValue, int x, int y, int width) {
super(name, field, initialValue, x, y, width);
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.drawText(textRenderer, name, x + 5, y + (OPTION_HEIGHT - 8) / 2, 0xFFFFFF, true);
int buttonX = x + width - BUTTON_WIDTH - 5;
int buttonY = y + (OPTION_HEIGHT - BUTTON_HEIGHT) / 2;
boolean hovered = isButtonHovered(mouseX, mouseY);
int buttonColor = hovered ? 0xFF404040 : 0xFF303030;
int buttonBorder = hovered ? 0xFFCCCCCC : 0xFF808080;
context.fill(buttonX, buttonY, buttonX + BUTTON_WIDTH, buttonY + BUTTON_HEIGHT, buttonBorder);
context.fill(buttonX + 1, buttonY + 1, buttonX + BUTTON_WIDTH - 1, buttonY + BUTTON_HEIGHT - 1, buttonColor);
String buttonText = value ? "true" : "false";
int textColor = value ? 0x00be00 : 0xbe0000;
int textWidth = textRenderer.getWidth(buttonText);
context.drawText(
textRenderer,
buttonText,
buttonX + (BUTTON_WIDTH - textWidth) / 2,
buttonY + (BUTTON_HEIGHT - 8) / 2,
textColor,
true
);
}
public boolean isButtonHovered(double mouseX, double mouseY) {
int buttonX = x + width - BUTTON_WIDTH - 5;
int buttonY = y + (OPTION_HEIGHT - BUTTON_HEIGHT) / 2;
return mouseX >= buttonX && mouseX <= buttonX + BUTTON_WIDTH &&
mouseY >= buttonY && mouseY <= buttonY + BUTTON_HEIGHT;
}
@Override
public void onClick(double mouseX, double mouseY) {
if (isButtonHovered(mouseX, mouseY)) {
value = !value;
saveValue();
}
}
@Override
protected void saveValue() {
try {
// Update config and save
field.setBoolean(ClientConfigManager.getConfig(), value);
ClientConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Implementation for String config options
*/
private class StringConfigOption extends ConfigOption<String> {
private static final int FIELD_WIDTH = 60;
private static final int FIELD_HEIGHT = 16;
protected TextFieldWidget textField;
public StringConfigOption(String name, Field field, String initialValue, int x, int y, int width) {
super(name, field, initialValue, x, y, width);
}
public void createTextField(net.minecraft.client.MinecraftClient client) {
int fieldX = x + width - FIELD_WIDTH - 5;
int fieldY = y + (OPTION_HEIGHT - FIELD_HEIGHT) / 2;
textField = new TextFieldWidget(
textRenderer,
fieldX,
fieldY,
FIELD_WIDTH,
FIELD_HEIGHT,
Text.literal("")
);
textField.setText(value);
textField.setMaxLength(10);
textField.setChangedListener(this::onTextChanged);
}
public TextFieldWidget getTextField() {
return textField;
}
public void updateTextFieldPosition() {
if (textField != null) {
int fieldX = x + width - FIELD_WIDTH - 5;
int fieldY = y + (OPTION_HEIGHT - FIELD_HEIGHT) / 2;
textField.setX(fieldX);
textField.setY(fieldY);
}
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.drawText(textRenderer, name, x + 5, y + (OPTION_HEIGHT - 8) / 2, 0xFFFFFF, true);
if (textField != null) {
textField.render(context, mouseX, mouseY, delta);
}
}
@Override
public void onClick(double mouseX, double mouseY) {
}
public void onTextChanged(String text) {
try {
value = text;
saveValue();
} catch (NumberFormatException e) {}
}
@Override
protected void saveValue() {
try {
field.set(ClientConfigManager.getConfig(), value);
ClientConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Base class for numeric config options (int and float)
*/
private abstract class NumericConfigOption<T extends Number> extends ConfigOption<T> {
private static final int FIELD_WIDTH = 60;
private static final int FIELD_HEIGHT = 16;
protected TextFieldWidget textField;
public NumericConfigOption(String name, Field field, T initialValue, int x, int y, int width) {
super(name, field, initialValue, x, y, width);
}
public void createTextField(net.minecraft.client.MinecraftClient client) {
int fieldX = x + width - FIELD_WIDTH - 5;
int fieldY = y + (OPTION_HEIGHT - FIELD_HEIGHT) / 2;
textField = new TextFieldWidget(
textRenderer,
fieldX,
fieldY,
FIELD_WIDTH,
FIELD_HEIGHT,
Text.literal("")
);
textField.setText(value.toString());
textField.setMaxLength(10);
textField.setChangedListener(this::onTextChanged);
}
public TextFieldWidget getTextField() {
return textField;
}
public void updateTextFieldPosition() {
if (textField != null) {
int fieldX = x + width - FIELD_WIDTH - 5;
int fieldY = y + (OPTION_HEIGHT - FIELD_HEIGHT) / 2;
textField.setX(fieldX);
textField.setY(fieldY);
}
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.drawText(textRenderer, name, x + 5, y + (OPTION_HEIGHT - 8) / 2, 0xFFFFFF, true);
if (textField != null) {
textField.render(context, mouseX, mouseY, delta);
}
}
@Override
public void onClick(double mouseX, double mouseY) {}
protected abstract void onTextChanged(String text);
}
/**
* Implementation for integer config options
*/
private class IntegerConfigOption extends NumericConfigOption<Integer> {
public IntegerConfigOption(String name, Field field, Integer initialValue, int x, int y, int width) {
super(name, field, initialValue, x, y, width);
}
@Override
protected void onTextChanged(String text) {
try {
value = Integer.parseInt(text);
saveValue();
} catch (NumberFormatException e) {}
}
@Override
protected void saveValue() {
try {
field.setInt(ClientConfigManager.getConfig(), value);
ClientConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Implementation for float config options
*/
private class FloatConfigOption extends NumericConfigOption<Float> {
public FloatConfigOption(String name, Field field, Float initialValue, int x, int y, int width) {
super(name, field, initialValue, x, y, width);
}
@Override
protected void onTextChanged(String text) {
try {
value = Float.parseFloat(text);
saveValue();
} catch (NumberFormatException e) {}
}
@Override
protected void saveValue() {
try {
field.setFloat(ClientConfigManager.getConfig(), value);
ClientConfigManager.save();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@@ -10,6 +10,6 @@ public class ModMenuIntegration implements ModMenuApi {
@Override @Override
public ConfigScreenFactory<?> getModConfigScreenFactory() { public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ConfigScreen::new; return YACLConfigScreen::createConfigScreen;
} }
} }

View File

@@ -0,0 +1,231 @@
package wtf.hak.survivalfabric.modmenu;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.*;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
public class YACLConfigScreen {
public static Screen createConfigScreen(Screen parent) {
return YetAnotherConfigLib.createBuilder()
.title(Text.literal("Survival Fabric Configuration").formatted(Formatting.BOLD))
.category(createMainCategory())
.save(ClientConfigManager::save)
.build()
.generateScreen(parent);
}
private static ConfigCategory createMainCategory() {
ConfigCategory.Builder categoryBuilder = ConfigCategory.createBuilder()
.name(Text.literal("General Settings").formatted(Formatting.YELLOW))
.tooltip(Text.literal("Common configuration options for Survival Fabric"));
// Create groups for different types of options
OptionGroup.Builder renderingGroup = OptionGroup.createBuilder()
.name(Text.literal("Rendering Options"))
.description(OptionDescription.of(Text.literal("Options that control fog rendering and visual effects")))
.collapsed(false);
OptionGroup.Builder gameplayGroup = OptionGroup.createBuilder()
.name(Text.literal("Gameplay Options"))
.description(OptionDescription.of(Text.literal("Options that affect gameplay mechanics")))
.collapsed(false);
OptionGroup.Builder cameraGroup = OptionGroup.createBuilder()
.name(Text.literal("Camera & Zoom"))
.description(OptionDescription.of(Text.literal("Camera and zoom-related settings")))
.collapsed(false);
// === RENDERING OPTIONS ===
// Nether Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render Nether Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog rendering in the Nether dimension")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderNetherFog,
newValue -> ClientConfigManager.getConfig().renderNetherFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Overworld Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render Overworld Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog rendering in the Overworld dimension")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderOverworldFog,
newValue -> ClientConfigManager.getConfig().renderOverworldFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// End Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render End Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog rendering in the End dimension")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderEndFog,
newValue -> ClientConfigManager.getConfig().renderEndFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Lava Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render Lava Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog when submerged in lava")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderLavaFog,
newValue -> ClientConfigManager.getConfig().renderLavaFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Water Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render Water Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog when submerged in water")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderWaterFog,
newValue -> ClientConfigManager.getConfig().renderWaterFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Snow Fog
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Render Snow Fog"))
.description(OptionDescription.of(Text.literal("Enable or disable fog during snow weather")))
.binding(
false, // default
() -> ClientConfigManager.getConfig().renderSnowFog,
newValue -> ClientConfigManager.getConfig().renderSnowFog = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Remove Darkness Effect
renderingGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Remove Darkness Effect"))
.description(OptionDescription.of(Text.literal("Remove the darkness effect applied by the Warden and sculk shriekers")))
.binding(
true, // default
() -> ClientConfigManager.getConfig().removeDarknessEffect,
newValue -> ClientConfigManager.getConfig().removeDarknessEffect = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// === GAMEPLAY OPTIONS ===
// Lock Teleport Head Movement
gameplayGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Lock Teleport Head Movement"))
.description(OptionDescription.of(Text.literal("Prevent head movement changes during teleportation")))
.binding(
true, // default
() -> ClientConfigManager.getConfig().lockTeleportHeadMovement,
newValue -> ClientConfigManager.getConfig().lockTeleportHeadMovement = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Manipulate Block Entity Distance
gameplayGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Manipulate Block Entity Distance"))
.description(OptionDescription.of(Text.literal("Enable custom block entity rendering distance")))
.binding(
true, // default
() -> ClientConfigManager.getConfig().manipulateBlockEntityDistance,
newValue -> ClientConfigManager.getConfig().manipulateBlockEntityDistance = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Block Entity Range
gameplayGroup.option(Option.<Integer>createBuilder()
.name(Text.literal("Block Entity Range"))
.description(OptionDescription.of(Text.literal("Maximum distance for block entity rendering (in blocks)")))
.binding(
512, // default
() -> ClientConfigManager.getConfig().blockEntityRange,
newValue -> ClientConfigManager.getConfig().blockEntityRange = newValue
)
.controller(opt -> IntegerSliderControllerBuilder.create(opt)
.range(16, 2048)
.step(16)
.formatValue(value -> Text.literal(value + " blocks")))
.build());
// === CAMERA & ZOOM OPTIONS ===
// Smooth Camera
cameraGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Smooth Camera"))
.description(OptionDescription.of(Text.literal("Enable smooth camera transitions")))
.binding(
true, // default
() -> ClientConfigManager.getConfig().smoothCamera,
newValue -> ClientConfigManager.getConfig().smoothCamera = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Initial Zoom
cameraGroup.option(Option.<Float>createBuilder()
.name(Text.literal("Initial Zoom Level"))
.description(OptionDescription.of(Text.literal("Default zoom level when activating zoom")))
.binding(
20f, // default
() -> ClientConfigManager.getConfig().initialZoom,
newValue -> ClientConfigManager.getConfig().initialZoom = newValue
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(1.0f, 50.0f)
.step(0.5f)
.formatValue(value -> Text.literal(String.format("%.1fx", value))))
.build());
// Scroll to Zoom
cameraGroup.option(Option.<Boolean>createBuilder()
.name(Text.literal("Scroll to Zoom"))
.description(OptionDescription.of(Text.literal("Allow using mouse wheel to adjust zoom level")))
.binding(
true, // default
() -> ClientConfigManager.getConfig().scrollToZoom,
newValue -> ClientConfigManager.getConfig().scrollToZoom = newValue
)
.controller(TickBoxControllerBuilder::create)
.build());
// Zoom Step
cameraGroup.option(Option.<Float>createBuilder()
.name(Text.literal("Zoom Step"))
.description(OptionDescription.of(Text.literal("Amount of zoom change per scroll wheel step")))
.binding(
2.5f, // default
() -> ClientConfigManager.getConfig().zoomStep,
newValue -> ClientConfigManager.getConfig().zoomStep = newValue
)
.controller(opt -> FloatSliderControllerBuilder.create(opt)
.range(0.1f, 10.0f)
.step(0.1f)
.formatValue(value -> Text.literal(String.format("%.1f", value))))
.build());
return categoryBuilder
.group(renderingGroup.build())
.group(gameplayGroup.build())
.group(cameraGroup.build())
.build();
}
}

View File

@@ -3,13 +3,17 @@
"package": "wtf.hak.survivalfabric.mixin.client", "package": "wtf.hak.survivalfabric.mixin.client",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"client": [ "client": [
"BackgroundRendererMixin",
"BlockEntityRendererMixin", "BlockEntityRendererMixin",
"EntityMixin", "EntityMixin",
"FogMixins$DimensionOrBossFogModifierMixin",
"FogMixins$LavaFogModifierMixin",
"FogMixins$PowderSnowFogModifierMixin",
"FogMixins$WaterFogModifierMixin",
"GameRendererMixin", "GameRendererMixin",
"MouseMixin" "MouseMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
} },
"mixins": []
} }

View File

@@ -27,8 +27,8 @@ public class SlimeChunkCommand {
return 0; return 0;
} }
ServerPlayerEntity p = (ServerPlayerEntity) source.getEntity(); ServerPlayerEntity p = (ServerPlayerEntity) source.getEntity();
Chunk chunk = p.getServerWorld().getChunk(p.getBlockPos()); Chunk chunk = p.getWorld().getChunk(p.getBlockPos());
Random slimeRandom = ChunkRandom.getSlimeRandom(chunk.getPos().x, chunk.getPos().z, p.getServerWorld().getSeed(), 987234911L); Random slimeRandom = ChunkRandom.getSlimeRandom(chunk.getPos().x, chunk.getPos().z, p.getWorld().getSeed(), 987234911L);
if (slimeRandom.nextInt(10) == 0) { if (slimeRandom.nextInt(10) == 0) {
p.sendMessage(Text.literal(getConfig().inSlimeChunkMessage)); p.sendMessage(Text.literal(getConfig().inSlimeChunkMessage));
} else } else

View File

@@ -45,7 +45,7 @@ public class SpectatorCommand {
.getZ(), player .getZ(), player
.getYaw(), player .getYaw(), player
.getPitch(), player .getPitch(), player
.getServerWorld())); .getWorld()));
player.changeGameMode(GameMode.SPECTATOR); player.changeGameMode(GameMode.SPECTATOR);
PacketUtils.updateListNames(player); PacketUtils.updateListNames(player);
} }

View File

@@ -6,11 +6,8 @@ import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.block.EnderChestBlock; import net.minecraft.block.EnderChestBlock;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.*;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtSizeTracker;
import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.GenericContainerScreenHandler;
import net.minecraft.screen.SimpleNamedScreenHandlerFactory; import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
@@ -24,6 +21,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.io.*; import java.io.*;
import java.util.Optional;
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER; import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
@@ -38,7 +36,23 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
File inventoryFile = getFile(server); File inventoryFile = getFile(server);
NbtCompound nbt = new NbtCompound(); NbtCompound nbt = new NbtCompound();
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY); DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
Inventories.writeNbt(nbt, sharedInventory.getList(inventoryItemStacks), server.getRegistryManager()); inventoryItemStacks = sharedInventory.getList(inventoryItemStacks);
NbtList itemsList = new NbtList();
for (int i = 0; i < inventoryItemStacks.size(); i++) {
ItemStack stack = inventoryItemStacks.get(i);
if (!stack.isEmpty()) {
NbtCompound itemNbt = new NbtCompound();
itemNbt.putInt("Slot", i);
itemNbt.copyFromCodec(ItemStack.MAP_CODEC,
server.getRegistryManager().getOps(NbtOps.INSTANCE), stack);
itemsList.add(itemNbt);
}
}
nbt.put("Items", itemsList);
try (FileOutputStream inventoryFileOutputStream = new FileOutputStream(inventoryFile); try (FileOutputStream inventoryFileOutputStream = new FileOutputStream(inventoryFile);
DataOutputStream inventoryFileDataOutput = new DataOutputStream(inventoryFileOutputStream)) { DataOutputStream inventoryFileDataOutput = new DataOutputStream(inventoryFileOutputStream)) {
inventoryFile.createNewFile(); inventoryFile.createNewFile();
@@ -79,6 +93,7 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
return server.getSavePath(WorldSavePath.ROOT).resolve("sharedenderchest.sav").toFile(); return server.getSavePath(WorldSavePath.ROOT).resolve("sharedenderchest.sav").toFile();
} }
public void onServerStarted(MinecraftServer server) { public void onServerStarted(MinecraftServer server) {
File inventoryFile = getFile(server); File inventoryFile = getFile(server);
if (inventoryFile.exists()) { if (inventoryFile.exists()) {
@@ -86,7 +101,22 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
DataInputStream inventoryFileDataInput = new DataInputStream(inventoryFileInputStream)) { DataInputStream inventoryFileDataInput = new DataInputStream(inventoryFileInputStream)) {
NbtCompound nbt = NbtIo.readCompressed(inventoryFileDataInput, NbtSizeTracker.ofUnlimitedBytes()); NbtCompound nbt = NbtIo.readCompressed(inventoryFileDataInput, NbtSizeTracker.ofUnlimitedBytes());
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY); DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
Inventories.readNbt(nbt, inventoryItemStacks, server.getRegistryManager());
// Load each item using the new codec system
if (nbt.contains("Items")) {
NbtList itemsList = nbt.getList("Items").get();
for (int i = 0; i < itemsList.size(); i++) {
NbtCompound itemNbt = itemsList.getCompound(i).get();
int slot = itemNbt.getInt("Slot").get();
if (slot >= 0 && slot < inventoryItemStacks.size()) {
// Use ItemStack's codec for deserialization
Optional<ItemStack> stackOpt = itemNbt.decode(ItemStack.MAP_CODEC,
server.getRegistryManager().getOps(NbtOps.INSTANCE));
stackOpt.ifPresent(stack -> inventoryItemStacks.set(slot, stack));
}
}
}
sharedInventory = new SharedInventory(inventoryItemStacks); sharedInventory = new SharedInventory(inventoryItemStacks);
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Error while loading Shared Ender Chest: " + e); LOGGER.error("Error while loading Shared Ender Chest: " + e);

View File

@@ -19,7 +19,7 @@ public class ServerPlayerEntityMixin {
private void changePlayerListName(CallbackInfoReturnable<Text> cir) { private void changePlayerListName(CallbackInfoReturnable<Text> cir) {
if (ConfigManager.getConfig().dimensionIndicatorEnabled) { if (ConfigManager.getConfig().dimensionIndicatorEnabled) {
ServerPlayerEntity p = (ServerPlayerEntity) (Object) this; ServerPlayerEntity p = (ServerPlayerEntity) (Object) this;
String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey(); String world = p.getWorld().getRegistryKey().getValue().toTranslationKey();
String finalName; String finalName;
if (!SpectatorCommand.spectating.containsKey(p)) { if (!SpectatorCommand.spectating.containsKey(p)) {
finalName = switch (world) { finalName = switch (world) {

View File

@@ -38,9 +38,10 @@
], ],
"depends": { "depends": {
"fabricloader": ">=0.16.10", "fabricloader": ">=0.16.10",
"minecraft": "~1.21.5", "minecraft": "~1.21.6",
"java": ">=21", "java": ">=21",
"fabric-api": "*" "fabric-api": "*",
"yet_another_config_lib_v3": ">=3.7.0"
}, },
"optional": { "optional": {
"modmenu": "*" "modmenu": "*"