Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
92e9c996b9 | |||
dd50ff0376 | |||
d2fd497f26 | |||
a6f8645c68 | |||
2be7c16e8b | |||
ccbb15f7c4 | |||
05a93b5ab0 | |||
327c92a81c | |||
29067365ca | |||
1ae193ada7 | |||
151fd022c4 | |||
deb338f2c8 | |||
61d2a0b137 | |||
35148c2159 | |||
ef29652e07 | |||
ee448ae34d | |||
69df0e5850 | |||
4988a91e70 | |||
8808df1cf1 | |||
963002570d | |||
2c95b5e374 | |||
1d3dba1b02 | |||
c7ea933494 | |||
be6472cc79 | |||
49ee0c503f | |||
f994e8cc11 | |||
8498a77735 | |||
0dd5907a29 | |||
c25790902c | |||
f51bde3d6e | |||
563febe36f | |||
38a244b023 | |||
868e71eb10 | |||
2a106c9a03 | |||
1d0f7f0f05 | |||
52a97dc2c1 | |||
5916cc5cc2 | |||
4e58bede4b | |||
130b2db727 | |||
c993621bdb | |||
b89ab99928 |
51
README.md
51
README.md
@ -8,18 +8,17 @@ As a challenge I'm trying to make it as user-friendly as possible.
|
|||||||
|
|
||||||
## Server Side
|
## Server Side
|
||||||
|
|
||||||
### Features
|
|
||||||
- Custom join message
|
- Custom join message
|
||||||

|

|
||||||
- Custom quit message
|
- Custom quit message
|
||||||

|

|
||||||
- Custom chat message
|
- Custom chat message
|
||||||

|

|
||||||
- Tab list dimension indicator
|
- Tab list dimension indicator
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
- Config
|
- Config
|
||||||
- Configurable messages
|
- Configurable messages
|
||||||
- Feature toggle
|
- Feature toggle
|
||||||
@ -28,9 +27,15 @@ As a challenge I'm trying to make it as user-friendly as possible.
|
|||||||
- Shared EC Access control (via config)
|
- Shared EC Access control (via config)
|
||||||
- Open/close EC block while opening/closing SEC
|
- Open/close EC block while opening/closing SEC
|
||||||
- Play open & close sounds
|
- Play open & close sounds
|
||||||
- Vein miner
|
- Vein miner
|
||||||

|
- Configurable animation (tick delay)
|
||||||
|
- Leaves veinmineable using shears
|
||||||
|
Code inspired by Inferis!
|
||||||
|

|
||||||
- Chat Calculator
|
- Chat Calculator
|
||||||
|
- Check if operator is present before calculating
|
||||||
|
- Replenish
|
||||||
|
|
||||||
|
|
||||||
### Commands
|
### Commands
|
||||||
- /spectator | Essentially server-side free-cam, you get put in spectator and are able to fly around, once you use the command again you get put back to where you were.
|
- /spectator | Essentially server-side free-cam, you get put in spectator and are able to fly around, once you use the command again you get put back to where you were.
|
||||||
@ -43,18 +48,34 @@ As a challenge I'm trying to make it as user-friendly as possible.
|
|||||||

|

|
||||||
- Remove game fog (lava, water, etc.)
|
- Remove game fog (lava, water, etc.)
|
||||||
- All types individually toggleable
|
- All types individually toggleable
|
||||||
- Mod Menu integration
|
|
||||||
- Automatic config adaption (currently booleans only)
|
|
||||||
- Remove darkness effect
|
- Remove darkness effect
|
||||||
- Toggleable
|
- Toggleable
|
||||||
- Keybinding for /camera
|
- Keybinding for /spectator
|
||||||
|
- Render block entities from a longer range
|
||||||
|
- Toggleable via GUI
|
||||||
|
- Configurable value
|
||||||
|
- In GUI
|
||||||
|
- Mod Menu integration
|
||||||
|
DISCLAIMER: this is NOT perfect and still needs to be reworked, I'm just too lazy right now...
|
||||||
|
- The following types are accepted:
|
||||||
|
- String
|
||||||
|
- Boolean
|
||||||
|
- Float
|
||||||
|
- Integer
|
||||||
|
- Zoom
|
||||||
|
- Configurable
|
||||||
|
- Smooth zoom
|
||||||
|
- Initial zoom value
|
||||||
|
- Zoom step value
|
||||||
|
- Scroll to zoom further
|
||||||
|
|
||||||
# To-do
|
# To-do
|
||||||
|
|
||||||
## General
|
## General
|
||||||
- Rework config system
|
- Rework config system
|
||||||
- Store server settings in world folder for better singleplayer use
|
- Store server settings in world folder for better singleplayer use
|
||||||
- Rework Mod Menu integration to be more flexible
|
|
||||||
|
## Client side
|
||||||
|
|
||||||
## Server Side
|
## Server Side
|
||||||
- Telekinesis
|
- Telekinesis
|
||||||
|
@ -4,16 +4,16 @@ 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.4
|
||||||
yarn_mappings=1.21.5+build.1
|
yarn_mappings=1.21.4+build.8
|
||||||
loader_version=0.16.10
|
loader_version=0.16.10
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=1.4.0
|
mod_version=1.4.1
|
||||||
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.119.0+1.21.4
|
||||||
|
|
||||||
modmenu_version=14.0.0-rc.2
|
modmenu_version=13.0.3
|
@ -4,7 +4,8 @@ import net.fabricmc.api.ClientModInitializer;
|
|||||||
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
||||||
import wtf.hak.survivalfabric.features.AngleViewer;
|
import wtf.hak.survivalfabric.features.AngleViewer;
|
||||||
import wtf.hak.survivalfabric.features.RemoveDarknessEffect;
|
import wtf.hak.survivalfabric.features.RemoveDarknessEffect;
|
||||||
import wtf.hak.survivalfabric.features.SFKeyBindings;
|
import wtf.hak.survivalfabric.features.CameraShortcut;
|
||||||
|
import wtf.hak.survivalfabric.features.Zoom;
|
||||||
|
|
||||||
public class SurvivalFabricClient implements ClientModInitializer {
|
public class SurvivalFabricClient implements ClientModInitializer {
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ public class SurvivalFabricClient implements ClientModInitializer {
|
|||||||
// Features
|
// Features
|
||||||
AngleViewer.register();
|
AngleViewer.register();
|
||||||
RemoveDarknessEffect.register();
|
RemoveDarknessEffect.register();
|
||||||
SFKeyBindings.register();
|
CameraShortcut.register();
|
||||||
|
Zoom.register();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package wtf.hak.survivalfabric.config.client;
|
package wtf.hak.survivalfabric.config.client;
|
||||||
|
|
||||||
public class ClientConfig {
|
public class ClientConfig {
|
||||||
public String configVersion = "1.0";
|
public String configVersion = "1.1";
|
||||||
|
|
||||||
public boolean renderNetherFog = false;
|
public boolean renderNetherFog = false;
|
||||||
public boolean renderOverworldFog = false;
|
public boolean renderOverworldFog = false;
|
||||||
@ -11,4 +11,10 @@ public class ClientConfig {
|
|||||||
public boolean renderSnowFog = false;
|
public boolean renderSnowFog = false;
|
||||||
public boolean removeDarknessEffect = true;
|
public boolean removeDarknessEffect = true;
|
||||||
public boolean lockTeleportHeadMovement = true;
|
public boolean lockTeleportHeadMovement = true;
|
||||||
|
public boolean manipulateBlockEntityDistance = true;
|
||||||
|
public int blockEntityRange = 512;
|
||||||
|
public boolean smoothCamera = true;
|
||||||
|
public float initialZoom = 20f;
|
||||||
|
public boolean scrollToZoom = true;
|
||||||
|
public float zoomStep = 2.5f;
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ import net.minecraft.client.option.KeyBinding;
|
|||||||
import net.minecraft.client.util.InputUtil;
|
import net.minecraft.client.util.InputUtil;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
public class SFKeyBindings {
|
public class CameraShortcut {
|
||||||
|
|
||||||
private static final KeyBinding CAMERA_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.camera", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_PERIOD, "category.survivalfabric.survivalfabric"));
|
private static final KeyBinding CAMERA_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.camera", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_PERIOD, "category.survivalfabric.survivalfabric"));
|
||||||
|
|
||||||
public static void register() {
|
public static void register() {
|
||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
if(client.player != null) {
|
if (client.player != null) {
|
||||||
if (CAMERA_BIND.wasPressed()) {
|
if (CAMERA_BIND.wasPressed()) {
|
||||||
client.player.networkHandler.sendChatCommand("camera");
|
client.player.networkHandler.sendChatCommand("camera");
|
||||||
}
|
}
|
63
src/client/java/wtf/hak/survivalfabric/features/Zoom.java
Normal file
63
src/client/java/wtf/hak/survivalfabric/features/Zoom.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package wtf.hak.survivalfabric.features;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.option.KeyBinding;
|
||||||
|
import net.minecraft.client.util.InputUtil;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
|
||||||
|
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
||||||
|
|
||||||
|
public class Zoom {
|
||||||
|
|
||||||
|
private static final KeyBinding ZOOM_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.zoom", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_C, "category.survivalfabric.survivalfabric"));
|
||||||
|
|
||||||
|
private static boolean SHOULD_ZOOM = false;
|
||||||
|
private static int ZOOM_STEP = 0;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static boolean initialSmoothZoom;
|
||||||
|
|
||||||
|
public static void register() {
|
||||||
|
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||||
|
|
||||||
|
if (ZOOM_BIND.isPressed() && !SHOULD_ZOOM) {
|
||||||
|
if(getConfig().smoothCamera) {
|
||||||
|
initialSmoothZoom = MinecraftClient.getInstance().options.smoothCameraEnabled;
|
||||||
|
MinecraftClient.getInstance().options.smoothCameraEnabled = true;
|
||||||
|
}
|
||||||
|
SHOULD_ZOOM = true;
|
||||||
|
} else if (!ZOOM_BIND.isPressed() && SHOULD_ZOOM) {
|
||||||
|
SHOULD_ZOOM = false;
|
||||||
|
ZOOM_STEP = 0;
|
||||||
|
if(getConfig().smoothCamera) {
|
||||||
|
MinecraftClient.getInstance().options.smoothCameraEnabled = initialSmoothZoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isZooming() {
|
||||||
|
return SHOULD_ZOOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getZoomFov() {
|
||||||
|
return getConfig().initialZoom - -ZOOM_STEP * getConfig().zoomStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void modifyStep(int step) {
|
||||||
|
ZOOM_STEP += step;
|
||||||
|
|
||||||
|
// Clamp the zoom level so the FOV stays within [1, 110]
|
||||||
|
float zoomFov = getZoomFov();
|
||||||
|
if (zoomFov < 1) {
|
||||||
|
ZOOM_STEP = Math.round((1 - getConfig().initialZoom) / getConfig().zoomStep) + 1;
|
||||||
|
} else if (zoomFov > 110) {
|
||||||
|
ZOOM_STEP = Math.round((110 - getConfig().initialZoom) / getConfig().zoomStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||||||
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
||||||
|
|
||||||
@Mixin(value = BackgroundRenderer.class, priority = 910)
|
@Mixin(value = BackgroundRenderer.class, priority = 910)
|
||||||
public abstract class BackgroundRendererMixin {
|
public class BackgroundRendererMixin {
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private static final Fog EMPTY_FOG = new Fog(-8.0f, 1_000_000.0F, FogShape.CYLINDER, 0, 0, 0, 0);
|
private static final Fog EMPTY_FOG = new Fog(-8.0f, 1_000_000.0F, FogShape.CYLINDER, 0, 0, 0, 0);
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package wtf.hak.survivalfabric.mixin.client;
|
||||||
|
|
||||||
|
import net.minecraft.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
|
||||||
|
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.CallbackInfoReturnable;
|
||||||
|
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
||||||
|
|
||||||
|
@Mixin(BlockEntityRenderer.class)
|
||||||
|
public interface BlockEntityRendererMixin<T extends BlockEntity> {
|
||||||
|
|
||||||
|
@Inject(method = "getRenderDistance", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void getRenderDistance(CallbackInfoReturnable<Integer> cir) {
|
||||||
|
if (ClientConfigManager.getConfig().manipulateBlockEntityDistance)
|
||||||
|
cir.setReturnValue(ClientConfigManager.getConfig().blockEntityRange);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package wtf.hak.survivalfabric.mixin.client;
|
||||||
|
|
||||||
|
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||||
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
|
import net.minecraft.client.render.GameRenderer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import wtf.hak.survivalfabric.features.Zoom;
|
||||||
|
|
||||||
|
@Mixin(GameRenderer.class)
|
||||||
|
public class GameRendererMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify Zoom FOV
|
||||||
|
*/
|
||||||
|
@ModifyReturnValue(method = "getFov", at = @At("RETURN"))
|
||||||
|
private float modifyFovWithZoom(float fov, @Local(argsOnly = true) float tickDelta) {
|
||||||
|
return Zoom.isZooming() ? Zoom.getZoomFov() : fov;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package wtf.hak.survivalfabric.mixin.client;
|
||||||
|
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.Mouse;
|
||||||
|
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 wtf.hak.survivalfabric.features.Zoom;
|
||||||
|
|
||||||
|
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
||||||
|
|
||||||
|
@Mixin(Mouse.class)
|
||||||
|
public class MouseMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolling listener for zooming
|
||||||
|
*/
|
||||||
|
@Inject(method = "onMouseScroll", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) {
|
||||||
|
if (Zoom.isZooming() && getConfig().scrollToZoom) {
|
||||||
|
if (MinecraftClient.getInstance().player != null) {
|
||||||
|
if (vertical > 0)
|
||||||
|
Zoom.modifyStep(-1);
|
||||||
|
else
|
||||||
|
Zoom.modifyStep(1);
|
||||||
|
}
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -3,15 +3,29 @@ package wtf.hak.survivalfabric.modmenu;
|
|||||||
import net.minecraft.client.gui.DrawContext;
|
import net.minecraft.client.gui.DrawContext;
|
||||||
import net.minecraft.client.gui.screen.Screen;
|
import net.minecraft.client.gui.screen.Screen;
|
||||||
import net.minecraft.client.gui.widget.ButtonWidget;
|
import net.minecraft.client.gui.widget.ButtonWidget;
|
||||||
|
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
import wtf.hak.survivalfabric.config.client.ClientConfig;
|
import wtf.hak.survivalfabric.config.client.ClientConfig;
|
||||||
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ConfigScreen extends Screen {
|
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 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) {
|
public ConfigScreen(Screen parent) {
|
||||||
super(Text.literal("Survival Fabric - Client Config"));
|
super(Text.literal("Survival Fabric - Client Config"));
|
||||||
@ -20,55 +34,272 @@ public class ConfigScreen extends Screen {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init() {
|
protected void init() {
|
||||||
int buttonWidth = 200;
|
options.clear();
|
||||||
int buttonHeight = 20;
|
|
||||||
int spacing = 5;
|
|
||||||
int startY = 40;
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
|
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()) {
|
for (Field field : ClientConfig.class.getFields()) {
|
||||||
if (field.getType() == boolean.class) {
|
try {
|
||||||
int y = startY + i * (buttonHeight + spacing);
|
Class<?> type = field.getType();
|
||||||
try {
|
String name = formatFieldName(field.getName());
|
||||||
|
|
||||||
|
ConfigOption<?> option = null;
|
||||||
|
if (type == boolean.class) {
|
||||||
boolean value = field.getBoolean(ClientConfigManager.getConfig());
|
boolean value = field.getBoolean(ClientConfigManager.getConfig());
|
||||||
String label = formatFieldName(field.getName()) + ": " + (value ? "ON" : "OFF");
|
option = new BooleanConfigOption(
|
||||||
|
name,
|
||||||
ButtonWidget button = ButtonWidget.builder(
|
field,
|
||||||
Text.literal(label),
|
value,
|
||||||
b -> {
|
listLeft,
|
||||||
try {
|
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
|
||||||
boolean current = field.getBoolean(ClientConfigManager.getConfig());
|
listWidth
|
||||||
field.setBoolean(ClientConfigManager.getConfig(), !current);
|
);
|
||||||
b.setMessage(Text.literal(formatFieldName(field.getName()) + ": " + (!current ? "ON" : "OFF")));
|
} else if (type == int.class) {
|
||||||
ClientConfigManager.save(); // Save if needed
|
int value = field.getInt(ClientConfigManager.getConfig());
|
||||||
} catch (Exception e) {
|
option = new IntegerConfigOption(
|
||||||
e.printStackTrace();
|
name,
|
||||||
}
|
field,
|
||||||
}
|
value,
|
||||||
).dimensions(this.width / 2 - buttonWidth / 2, y, buttonWidth, buttonHeight).build();
|
listLeft,
|
||||||
|
listTop + (index * OPTION_HEIGHT) - (int) scrollPosition,
|
||||||
this.addDrawableChild(button);
|
listWidth
|
||||||
i++;
|
);
|
||||||
} catch (Exception e) {
|
} else if (type == float.class) {
|
||||||
e.printStackTrace();
|
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(
|
this.addDrawableChild(ButtonWidget.builder(
|
||||||
Text.translatable("gui.done"),
|
Text.translatable("gui.done"),
|
||||||
button -> this.client.setScreen(parent)
|
button -> this.client.setScreen(parent)
|
||||||
).dimensions(this.width / 2 - 75, startY + i * (buttonHeight + spacing) + 10, 150, 20).build());
|
).dimensions(this.width / 2 - 100, this.height - 27, 200, 20).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
|
||||||
context.fill(0, 0, this.width, this.height, 0xC0101010);
|
this.renderBackground(context, mouseX, mouseY, delta);
|
||||||
|
|
||||||
int titleX = (this.width / 2) - (this.textRenderer.getWidth(this.title) / 2);
|
int titleX = (this.width / 2) - (this.textRenderer.getWidth(this.title) / 2);
|
||||||
context.drawTextWithShadow(this.textRenderer, this.title, titleX, 20, 0xFFFFFF);
|
context.drawText(this.textRenderer, this.title, titleX, 15, 0xFFFFFF, true);
|
||||||
|
|
||||||
super.render(context, mouseX, mouseY, delta);
|
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) {
|
private String formatFieldName(String rawName) {
|
||||||
@ -84,4 +315,285 @@ public class ConfigScreen extends Screen {
|
|||||||
return result.toString();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,10 @@
|
|||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "JAVA_21",
|
||||||
"client": [
|
"client": [
|
||||||
"BackgroundRendererMixin",
|
"BackgroundRendererMixin",
|
||||||
"EntityMixin"
|
"BlockEntityRendererMixin",
|
||||||
|
"EntityMixin",
|
||||||
|
"GameRendererMixin",
|
||||||
|
"MouseMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
@ -12,6 +12,7 @@ import wtf.hak.survivalfabric.commands.SlimeChunkCommand;
|
|||||||
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
||||||
import wtf.hak.survivalfabric.features.sharedenderchest.SharedEnderChest;
|
import wtf.hak.survivalfabric.features.sharedenderchest.SharedEnderChest;
|
||||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerEvents;
|
import wtf.hak.survivalfabric.features.veinminer.VeinMinerEvents;
|
||||||
|
import wtf.hak.survivalfabric.utils.Scheduler;
|
||||||
|
|
||||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ public class SurvivalFabric implements ModInitializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
Scheduler.initialize();
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, "spectator", "s", "S", "camera", "c", "C"));
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, "spectator", "s", "S", "camera", "c", "C"));
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, "reloadsurvivalconfig"));
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, "reloadsurvivalconfig"));
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, "slimechunk", "sc"));
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, "slimechunk", "sc"));
|
||||||
@ -32,7 +34,7 @@ public class SurvivalFabric implements ModInitializer {
|
|||||||
|
|
||||||
if (getConfig().veinMinerEnabled) {
|
if (getConfig().veinMinerEnabled) {
|
||||||
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
|
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
|
||||||
if (player instanceof ServerPlayerEntity serverPlayer) {
|
if (player instanceof ServerPlayerEntity serverPlayer && getConfig().veinMinerEnabled) {
|
||||||
return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state);
|
return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
|
@ -8,7 +8,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public class Config {
|
public class Config {
|
||||||
|
|
||||||
public String configVersion = "1.0";
|
public String configVersion = "1.1";
|
||||||
|
|
||||||
public boolean joinMessageEnabled = true;
|
public boolean joinMessageEnabled = true;
|
||||||
public String joinMessage = "§8[§a+§8] §7%s";
|
public String joinMessage = "§8[§a+§8] §7%s";
|
||||||
@ -29,17 +29,20 @@ public class Config {
|
|||||||
public boolean sharedEnderChestEnabled = true;
|
public boolean sharedEnderChestEnabled = true;
|
||||||
public String sharedEnderChestName = "Ender Chest";
|
public String sharedEnderChestName = "Ender Chest";
|
||||||
public int sharedEnderChestRows = 6;
|
public int sharedEnderChestRows = 6;
|
||||||
public boolean sharedEnderChestLimitedAccess = true;
|
public boolean sharedEnderChestLimitedAccess = false;
|
||||||
public List<String> sharedEnderChestNames = Lists.newArrayList("AlwaysHAK", "LunaticFox");
|
public List<String> sharedEnderChestNames = Lists.newArrayList("AlwaysHAK");
|
||||||
|
|
||||||
public String inSlimeChunkMessage = "§aYou're currently in a slime chunk";
|
public String inSlimeChunkMessage = "§aYou're currently in a slime chunk";
|
||||||
public String notInSlimeChunkMessage = "§cYou're currently not in a slime chunk";
|
public String notInSlimeChunkMessage = "§cYou're currently not in a slime chunk";
|
||||||
|
|
||||||
public boolean veinMinerEnabled = true;
|
public boolean veinMinerEnabled = true;
|
||||||
public int maxVeinSize = 99999;
|
public int maxVeinSize = 99999;
|
||||||
|
public long veinAnimationTicks = 0;
|
||||||
|
|
||||||
public boolean chatCalcEnabled = true;
|
public boolean chatCalcEnabled = true;
|
||||||
|
|
||||||
|
public boolean replenishEnabled = false;
|
||||||
|
|
||||||
public ScreenHandlerType<GenericContainerScreenHandler> screenHandlerType() {
|
public ScreenHandlerType<GenericContainerScreenHandler> screenHandlerType() {
|
||||||
return switch (sharedEnderChestRows) {
|
return switch (sharedEnderChestRows) {
|
||||||
case 1 -> ScreenHandlerType.GENERIC_9X1;
|
case 1 -> ScreenHandlerType.GENERIC_9X1;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package wtf.hak.survivalfabric.features.veinminer.drills;
|
package wtf.hak.survivalfabric.features.veinminer.drills;
|
||||||
|
|
||||||
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.block.BlockState;
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.registry.tag.TagKey;
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.text.MutableText;
|
import net.minecraft.text.MutableText;
|
||||||
import net.minecraft.text.PlainTextContent;
|
import net.minecraft.text.PlainTextContent;
|
||||||
@ -8,27 +10,77 @@ import net.minecraft.text.Text;
|
|||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import wtf.hak.survivalfabric.features.veinminer.Drill;
|
import wtf.hak.survivalfabric.features.veinminer.Drill;
|
||||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
||||||
|
import wtf.hak.survivalfabric.utils.Scheduler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
|
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
|
||||||
|
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
||||||
|
|
||||||
public class DrillBase implements Drill {
|
public class DrillBase implements Drill {
|
||||||
|
|
||||||
protected VeinMinerSession session;
|
protected VeinMinerSession session;
|
||||||
|
protected TagKey<Block> tag;
|
||||||
|
|
||||||
public DrillBase(VeinMinerSession session) {
|
public DrillBase(VeinMinerSession session, TagKey<Block> tag) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canHandle(BlockState blockId) {
|
public boolean canHandle(BlockState blockState) {
|
||||||
return false;
|
return blockState.isIn(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean drill(BlockPos blockPos) {
|
public boolean drill(BlockPos startPos) {
|
||||||
return false;
|
handleBlock(session.world.getBlockState(startPos).getBlock(), new HashSet<>(), startPos, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBlock(Block initialBlock, Set<BlockPos> history, BlockPos pos, int brokenBlocks) {
|
||||||
|
if (brokenBlocks < getConfig().maxVeinSize) {
|
||||||
|
history.add(pos);
|
||||||
|
if (tryBreakBlock(pos)) {
|
||||||
|
brokenBlocks++;
|
||||||
|
int finalBrokenBlocks = brokenBlocks;
|
||||||
|
|
||||||
|
// Put everything in a list to avoid scheduling a lot of tasks.
|
||||||
|
Set<BlockPos> toBreak = new HashSet<>();
|
||||||
|
collectAdjacentBlocks(pos, history, initialBlock, toBreak);
|
||||||
|
|
||||||
|
long delay = getConfig().veinAnimationTicks;
|
||||||
|
|
||||||
|
// Use final or effectively final variables for lambda
|
||||||
|
final int[] finalBrokenBlocksArr = {finalBrokenBlocks};
|
||||||
|
|
||||||
|
if (delay <= 0) {
|
||||||
|
handleBlocksInBatch(toBreak, initialBlock, history, finalBrokenBlocksArr[0]);
|
||||||
|
} else {
|
||||||
|
Scheduler.get().scheduleTask(() -> handleBlocksInBatch(toBreak, initialBlock, history, finalBrokenBlocksArr[0]), delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectAdjacentBlocks(BlockPos pos, Set<BlockPos> history, Block initialBlock, Set<BlockPos> toBreak) {
|
||||||
|
forXYZ(pos, 1, new ForXYZHandler() {
|
||||||
|
@Override
|
||||||
|
public void handle(BlockPos newPos) {
|
||||||
|
if (!history.contains(newPos) && session.world.getBlockState(newPos).getBlock() == initialBlock) {
|
||||||
|
toBreak.add(newPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBlocksInBatch(Set<BlockPos> toBreak, Block initialBlock, Set<BlockPos> history, int finalBrokenBlocks) {
|
||||||
|
for (BlockPos newPos : toBreak) {
|
||||||
|
handleBlock(initialBlock, history, newPos, finalBrokenBlocks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -38,9 +90,12 @@ public class DrillBase implements Drill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) {
|
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) {
|
||||||
forXYZ(pos, max, handlerPos -> {
|
forXYZ(pos, max, new ForXYZCounter() {
|
||||||
handler.handle(handlerPos);
|
@Override
|
||||||
return 0;
|
public int handle(BlockPos pos) {
|
||||||
|
handler.handle(pos);
|
||||||
|
return 1; // Accumulate one count for each position processed
|
||||||
|
}
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,14 +104,17 @@ public class DrillBase implements Drill {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) {
|
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) {
|
||||||
forXYZ(pos, max, handlerPos -> {
|
forXYZ(pos, max, new ForXYZCounter() {
|
||||||
handler.handle(handlerPos);
|
@Override
|
||||||
return 0;
|
public int handle(BlockPos pos) {
|
||||||
|
handler.handle(pos);
|
||||||
|
return 1; // Accumulate one count for each position processed
|
||||||
|
}
|
||||||
}, forceVertical);
|
}, forceVertical);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler, boolean forceVertical) {
|
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler, boolean forceVertical) {
|
||||||
ArrayList<Integer> offsets = new ArrayList<Integer>();
|
Set<Integer> offsets = new HashSet<>();
|
||||||
for (int d = 0; d <= max; ++d) {
|
for (int d = 0; d <= max; ++d) {
|
||||||
offsets.add(d);
|
offsets.add(d);
|
||||||
if (d != -d) {
|
if (d != -d) {
|
||||||
@ -64,39 +122,21 @@ public class DrillBase implements Drill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] order = new String[]{"x", "y", "z"};
|
String[] order = determineOrder(forceVertical);
|
||||||
if (forceVertical) {
|
|
||||||
order = new String[]{"y", "x", "z"};
|
|
||||||
} else {
|
|
||||||
ServerPlayerEntity player = session.player;
|
|
||||||
boolean majorPitchChange = player.getPitch() < -45.0 || player.getPitch() > 45.0;
|
|
||||||
boolean majorYawChange = (player.getYaw() > 45.0 && player.getYaw() < 135.0) || (player.getYaw() < -45.0 && player.getYaw() > -135.0);
|
|
||||||
if (majorPitchChange) {
|
|
||||||
if (majorYawChange) {
|
|
||||||
order = new String[]{"y", "z", "x"};
|
|
||||||
} else {
|
|
||||||
order = new String[]{"y", "x", "z"};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (majorYawChange) {
|
|
||||||
order = new String[]{"z", "y", "x"};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
|
||||||
for (int i1 : offsets) {
|
for (int i1 : offsets) {
|
||||||
for (int i2 : offsets) {
|
for (int i2 : offsets) {
|
||||||
for (int i3 : offsets) {
|
for (int i3 : offsets) {
|
||||||
int ix = order[0] == "x" ? i1 : order[1] == "x" ? i2 : i3;
|
int ix = order[0].equals("x") ? i1 : order[1].equals("x") ? i2 : i3;
|
||||||
int iy = order[0] == "y" ? i1 : order[1] == "y" ? i2 : i3;
|
int iy = order[0].equals("y") ? i1 : order[1].equals("y") ? i2 : i3;
|
||||||
int iz = order[0] == "z" ? i1 : order[1] == "z" ? i2 : i3;
|
int iz = order[0].equals("z") ? i1 : order[1].equals("z") ? i2 : i3;
|
||||||
|
|
||||||
int px = pos.getX() + ix;
|
int px = pos.getX() + ix;
|
||||||
int py = pos.getY() + iy;
|
int py = pos.getY() + iy;
|
||||||
int pz = pos.getZ() + iz;
|
int pz = pos.getZ() + iz;
|
||||||
|
|
||||||
counter += handler.handle(new BlockPos(px, py, pz));
|
counter += handler.handle(new BlockPos(px, py, pz)); // Works with ForXYZCounter, returning an int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +144,29 @@ public class DrillBase implements Drill {
|
|||||||
return counter;
|
return counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String[] determineOrder(boolean forceVertical) {
|
||||||
|
if (forceVertical) {
|
||||||
|
return new String[]{"y", "x", "z"};
|
||||||
|
} else {
|
||||||
|
ServerPlayerEntity player = session.player;
|
||||||
|
boolean majorPitchChange = player.getPitch() < -45.0 || player.getPitch() > 45.0;
|
||||||
|
boolean majorYawChange = (player.getYaw() > 45.0 && player.getYaw() < 135.0) || (player.getYaw() < -45.0 && player.getYaw() > -135.0);
|
||||||
|
|
||||||
|
if (majorPitchChange) {
|
||||||
|
if (majorYawChange) {
|
||||||
|
return new String[]{"y", "z", "x"};
|
||||||
|
} else {
|
||||||
|
return new String[]{"y", "x", "z"};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (majorYawChange) {
|
||||||
|
return new String[]{"z", "y", "x"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String[]{"x", "y", "z"};
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean tryBreakBlock(BlockPos blockPos) {
|
protected boolean tryBreakBlock(BlockPos blockPos) {
|
||||||
session.addPosition(blockPos);
|
session.addPosition(blockPos);
|
||||||
boolean success = isRightTool(blockPos) && session.player.interactionManager.tryBreakBlock(blockPos);
|
boolean success = isRightTool(blockPos) && session.player.interactionManager.tryBreakBlock(blockPos);
|
||||||
@ -127,4 +190,4 @@ public class DrillBase implements Drill {
|
|||||||
protected interface ForXYZCounter {
|
protected interface ForXYZCounter {
|
||||||
int handle(BlockPos pos);
|
int handle(BlockPos pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,22 @@
|
|||||||
package wtf.hak.survivalfabric.features.veinminer.drills;
|
package wtf.hak.survivalfabric.features.veinminer.drills;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.item.Items;
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
import net.minecraft.registry.RegistryKeys;
|
||||||
import net.minecraft.registry.tag.TagKey;
|
import net.minecraft.registry.tag.TagKey;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
|
|
||||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
|
||||||
|
|
||||||
public class LeavesDrill extends DrillBase {
|
public class LeavesDrill extends DrillBase {
|
||||||
|
|
||||||
public static final TagKey<Block> leavesTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves"));
|
|
||||||
|
|
||||||
public LeavesDrill(VeinMinerSession session) {
|
public LeavesDrill(VeinMinerSession session) {
|
||||||
super(session);
|
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canHandle(BlockState blockState) {
|
public boolean isRightTool(BlockPos pos) {
|
||||||
return blockState.isIn(leavesTag);
|
var blockState = session.world.getBlockState(pos);
|
||||||
}
|
return session.player.getMainHandStack().isSuitableFor(blockState) || session.player.getMainHandStack().getItem() == Items.SHEARS;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean drill(BlockPos startPos) {
|
|
||||||
ServerWorld world = session.world;
|
|
||||||
Block initialBlock = world.getBlockState(startPos).getBlock();
|
|
||||||
int brokenLeaves = 0;
|
|
||||||
ArrayDeque<BlockPos> pending = new ArrayDeque<BlockPos>();
|
|
||||||
pending.add(startPos);
|
|
||||||
|
|
||||||
while (!pending.isEmpty() && brokenLeaves < getConfig().maxVeinSize) {
|
|
||||||
BlockPos leavesPos = pending.remove();
|
|
||||||
Block leavesBlock = world.getBlockState(leavesPos).getBlock();
|
|
||||||
if (tryBreakBlock(leavesPos)) {
|
|
||||||
if (leavesBlock == initialBlock) {
|
|
||||||
brokenLeaves += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leavesBlock == initialBlock) {
|
|
||||||
// look around current block
|
|
||||||
forXYZ(leavesPos, 1, newPos -> {
|
|
||||||
BlockState newBlockState = world.getBlockState(newPos);
|
|
||||||
Block newBlock = newBlockState.getBlock();
|
|
||||||
boolean isSameOreBlock = newBlock == leavesBlock;
|
|
||||||
if (!pending.contains(newPos) && isSameOreBlock) {
|
|
||||||
pending.add(newPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,62 +1,14 @@
|
|||||||
package wtf.hak.survivalfabric.features.veinminer.drills;
|
package wtf.hak.survivalfabric.features.veinminer.drills;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
import net.minecraft.registry.RegistryKeys;
|
||||||
import net.minecraft.registry.tag.TagKey;
|
import net.minecraft.registry.tag.TagKey;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
|
|
||||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
|
||||||
|
|
||||||
public class OreDrill extends DrillBase {
|
public class OreDrill extends DrillBase {
|
||||||
|
|
||||||
public static final TagKey<Block> oreTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore"));
|
|
||||||
|
|
||||||
public OreDrill(VeinMinerSession session) {
|
public OreDrill(VeinMinerSession session) {
|
||||||
super(session);
|
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore")));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canHandle(BlockState blockState) {
|
|
||||||
return blockState.isIn(oreTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean drill(BlockPos startPos) {
|
|
||||||
ServerWorld world = session.world;
|
|
||||||
Block initialBlock = world.getBlockState(startPos).getBlock();
|
|
||||||
int brokenOre = 0;
|
|
||||||
ArrayDeque<BlockPos> pending = new ArrayDeque<BlockPos>();
|
|
||||||
pending.add(startPos);
|
|
||||||
|
|
||||||
while (!pending.isEmpty() && brokenOre < getConfig().maxVeinSize) {
|
|
||||||
BlockPos orePos = pending.remove();
|
|
||||||
Block oreBlock = world.getBlockState(orePos).getBlock();
|
|
||||||
if (tryBreakBlock(orePos)) {
|
|
||||||
if (oreBlock == initialBlock) {
|
|
||||||
brokenOre += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oreBlock == initialBlock) {
|
|
||||||
// look around current block
|
|
||||||
forXYZ(orePos, 1, newPos -> {
|
|
||||||
BlockState newBlockState = world.getBlockState(newPos);
|
|
||||||
Block newBlock = newBlockState.getBlock();
|
|
||||||
boolean isSameOreBlock = newBlock == oreBlock;
|
|
||||||
if (!pending.contains(newPos) && isSameOreBlock) {
|
|
||||||
pending.add(newPos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,80 +1,14 @@
|
|||||||
package wtf.hak.survivalfabric.features.veinminer.drills;
|
package wtf.hak.survivalfabric.features.veinminer.drills;
|
||||||
|
|
||||||
import net.minecraft.block.Block;
|
|
||||||
import net.minecraft.block.BlockState;
|
|
||||||
import net.minecraft.registry.Registries;
|
|
||||||
import net.minecraft.registry.RegistryKeys;
|
import net.minecraft.registry.RegistryKeys;
|
||||||
import net.minecraft.registry.tag.TagKey;
|
import net.minecraft.registry.tag.TagKey;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.math.BlockPos;
|
|
||||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
|
|
||||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
|
||||||
|
|
||||||
public class WoodDrill extends DrillBase {
|
public class WoodDrill extends DrillBase {
|
||||||
|
|
||||||
public static final TagKey<Block> woodTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood"));
|
|
||||||
|
|
||||||
public WoodDrill(VeinMinerSession session) {
|
public WoodDrill(VeinMinerSession session) {
|
||||||
super(session);
|
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood")));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canHandle(BlockState blockState) {
|
|
||||||
return blockState.isIn(woodTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean drill(BlockPos startPos) {
|
|
||||||
ServerWorld world = session.world;
|
|
||||||
int broken = 0;
|
|
||||||
ArrayDeque<BlockPos> pendingLogs = new ArrayDeque<>();
|
|
||||||
ArrayDeque<BlockPos> logBlocks = new ArrayDeque<>();
|
|
||||||
pendingLogs.add(startPos);
|
|
||||||
|
|
||||||
String leavesBlockId = Registries.BLOCK.getId(world.getBlockState(startPos).getBlock()).toString().replace("_log", "_leaves");
|
|
||||||
|
|
||||||
while (!pendingLogs.isEmpty() && broken < getConfig().maxVeinSize) {
|
|
||||||
BlockPos woodPos = pendingLogs.remove();
|
|
||||||
Block woodBlock = world.getBlockState(woodPos).getBlock();
|
|
||||||
|
|
||||||
if (tryBreakBlock(woodPos)) {
|
|
||||||
logBlocks.add(woodPos);
|
|
||||||
broken += 1;
|
|
||||||
|
|
||||||
// look around current block
|
|
||||||
forXYZ(woodPos, 1, newPos -> {
|
|
||||||
Block newBlock = world.getBlockState(newPos).getBlock();
|
|
||||||
if (newBlock == woodBlock && !pendingLogs.contains(newPos)) {
|
|
||||||
pendingLogs.add(newPos);
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayDeque<BlockPos> pendingLeaves = logBlocks;
|
|
||||||
while (!pendingLeaves.isEmpty() && broken < getConfig().maxVeinSize) {
|
|
||||||
broken += forXYZ(pendingLeaves.remove(), 1, newPos -> {
|
|
||||||
int brokenLeaves = 0;
|
|
||||||
Block newBlock = world.getBlockState(newPos).getBlock();
|
|
||||||
String newBlockId = Registries.BLOCK.getId(newBlock).toString();
|
|
||||||
if (newBlockId.equals(leavesBlockId)) {
|
|
||||||
if (tryBreakBlock(newPos)) {
|
|
||||||
brokenLeaves += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return brokenLeaves;
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLeaf(Block block) {
|
|
||||||
return Registries.BLOCK.getId(block).toString().endsWith("_leaves");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
64
src/main/java/wtf/hak/survivalfabric/mixin/BlockMixin.java
Normal file
64
src/main/java/wtf/hak/survivalfabric/mixin/BlockMixin.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package wtf.hak.survivalfabric.mixin;
|
||||||
|
|
||||||
|
import net.minecraft.block.Block;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.block.CropBlock;
|
||||||
|
import net.minecraft.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.item.HoeItem;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import net.minecraft.stat.Stats;
|
||||||
|
import net.minecraft.util.Hand;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.GameMode;
|
||||||
|
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.CallbackInfoReturnable;
|
||||||
|
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Mixin(Block.class)
|
||||||
|
public abstract class BlockMixin {
|
||||||
|
|
||||||
|
@Inject(method = "onBreak", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player, CallbackInfoReturnable<BlockState> cir) {
|
||||||
|
if (world.isClient()) return;
|
||||||
|
|
||||||
|
if (state.getBlock() instanceof CropBlock && ConfigManager.getConfig().replenishEnabled) {
|
||||||
|
ItemStack mainHand = player.getStackInHand(Hand.MAIN_HAND);
|
||||||
|
if (mainHand.getItem() instanceof HoeItem) {
|
||||||
|
Item seedItem = state.getBlock().asItem();
|
||||||
|
Block seedBlock = state.getBlock();
|
||||||
|
List<ItemStack> drops = Block.getDroppedStacks(state, (ServerWorld) world, pos, null, player, mainHand);
|
||||||
|
if (removeIfAvailable(drops, seedItem)) {
|
||||||
|
if (!player.isCreative()) {
|
||||||
|
for (ItemStack drop : drops) {
|
||||||
|
Block.dropStack(world, pos, drop);
|
||||||
|
}
|
||||||
|
player.incrementStat(Stats.USED.getOrCreateStat(seedItem));
|
||||||
|
mainHand.damage(1, player, EquipmentSlot.MAINHAND);
|
||||||
|
}
|
||||||
|
world.getServer().executeSync(() -> world.setBlockState(pos, seedBlock.getDefaultState()));
|
||||||
|
cir.setReturnValue(Blocks.AIR.getDefaultState());
|
||||||
|
cir.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeIfAvailable(List<ItemStack> drops, Item item) {
|
||||||
|
for (ItemStack drop : drops) {
|
||||||
|
if (drop.getItem() == item) {
|
||||||
|
drop.decrement(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -15,12 +15,16 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
|
|||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
||||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||||
|
import wtf.hak.survivalfabric.utils.MathUtils;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Mixin(PlayerManager.class)
|
@Mixin(PlayerManager.class)
|
||||||
public abstract class PlayerManagerMixin {
|
public abstract class PlayerManagerMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send join message to joined player
|
||||||
|
*/
|
||||||
@Inject(method = {"onPlayerConnect"}, at = {@At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")})
|
@Inject(method = {"onPlayerConnect"}, at = {@At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")})
|
||||||
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
|
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
|
||||||
if (ConfigManager.getConfig().joinMessageEnabled && !player.getServer().isSingleplayer()) {
|
if (ConfigManager.getConfig().joinMessageEnabled && !player.getServer().isSingleplayer()) {
|
||||||
@ -29,6 +33,9 @@ public abstract class PlayerManagerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify join message broadcasted to server
|
||||||
|
*/
|
||||||
@ModifyArg(method = {"onPlayerConnect"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
|
@ModifyArg(method = {"onPlayerConnect"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
|
||||||
private Text onPlayerConnect(Text text) {
|
private Text onPlayerConnect(Text text) {
|
||||||
if (ConfigManager.getConfig().joinMessageEnabled) {
|
if (ConfigManager.getConfig().joinMessageEnabled) {
|
||||||
@ -38,6 +45,9 @@ public abstract class PlayerManagerMixin {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get player out of specator mode if necessary
|
||||||
|
*/
|
||||||
@Inject(method = {"remove"}, at = {@At("HEAD")})
|
@Inject(method = {"remove"}, at = {@At("HEAD")})
|
||||||
public void onPlayerLeave(ServerPlayerEntity player, CallbackInfo ci) {
|
public void onPlayerLeave(ServerPlayerEntity player, CallbackInfo ci) {
|
||||||
if (SpectatorCommand.spectating.containsKey(player)) {
|
if (SpectatorCommand.spectating.containsKey(player)) {
|
||||||
@ -47,6 +57,9 @@ public abstract class PlayerManagerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify chat messages
|
||||||
|
*/
|
||||||
@Inject(method = {"broadcast(Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/network/message/MessageType$Parameters;)V"}, at = {@At("HEAD")}, cancellable = true)
|
@Inject(method = {"broadcast(Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/network/message/MessageType$Parameters;)V"}, at = {@At("HEAD")}, cancellable = true)
|
||||||
private void onBroadcast(SignedMessage message, ServerPlayerEntity sender, MessageType.Parameters parameters, CallbackInfo ci) {
|
private void onBroadcast(SignedMessage message, ServerPlayerEntity sender, MessageType.Parameters parameters, CallbackInfo ci) {
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
@ -56,10 +69,10 @@ public abstract class PlayerManagerMixin {
|
|||||||
|
|
||||||
String processedMessage = rawMessage;
|
String processedMessage = rawMessage;
|
||||||
|
|
||||||
if (isCalcEnabled) {
|
if (isCalcEnabled && MathUtils.hasSupportedOperator(rawMessage)) {
|
||||||
String expression = rawMessage.endsWith("=") ? rawMessage.substring(0, rawMessage.length() - 1).trim() : rawMessage;
|
String expression = rawMessage.endsWith("=") ? rawMessage.substring(0, rawMessage.length() - 1).trim() : rawMessage;
|
||||||
try {
|
try {
|
||||||
String result = String.valueOf(evaluateExpression(expression));
|
String result = String.valueOf(MathUtils.evaluateExpression(expression));
|
||||||
StringBuilder sb = new StringBuilder(rawMessage).append("§6");
|
StringBuilder sb = new StringBuilder(rawMessage).append("§6");
|
||||||
|
|
||||||
if (rawMessage.contains(" ")) sb.append(" ");
|
if (rawMessage.contains(" ")) sb.append(" ");
|
||||||
@ -83,47 +96,4 @@ public abstract class PlayerManagerMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double evaluateExpression(String expression) {
|
|
||||||
return evaluate(expression.replaceAll("\\s", ""), new int[]{0});
|
|
||||||
}
|
|
||||||
|
|
||||||
private double evaluate(String expr, int[] index) {
|
|
||||||
double value = parseTerm(expr, index);
|
|
||||||
while (index[0] < expr.length()) {
|
|
||||||
char op = expr.charAt(index[0]);
|
|
||||||
if (op != '+' && op != '-') break;
|
|
||||||
index[0]++;
|
|
||||||
double nextTerm = parseTerm(expr, index);
|
|
||||||
value = (op == '+') ? value + nextTerm : value - nextTerm;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double parseTerm(String expr, int[] index) {
|
|
||||||
double value = parseFactor(expr, index);
|
|
||||||
while (index[0] < expr.length()) {
|
|
||||||
char op = expr.charAt(index[0]);
|
|
||||||
if (op != '*' && op != '/') break;
|
|
||||||
index[0]++;
|
|
||||||
double nextFactor = parseFactor(expr, index);
|
|
||||||
value = (op == '*') ? value * nextFactor : value / nextFactor;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double parseFactor(String expr, int[] index) {
|
|
||||||
if (expr.charAt(index[0]) == '(') {
|
|
||||||
index[0]++;
|
|
||||||
double value = evaluate(expr, index);
|
|
||||||
index[0]++; // Skip closing ')'
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = index[0];
|
|
||||||
while (index[0] < expr.length() && (Character.isDigit(expr.charAt(index[0])) || expr.charAt(index[0]) == '.')) {
|
|
||||||
index[0]++;
|
|
||||||
}
|
|
||||||
return Double.parseDouble(expr.substring(start, index[0]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,11 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
|
|||||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||||
|
|
||||||
@Mixin(ServerPlayNetworkHandler.class)
|
@Mixin(ServerPlayNetworkHandler.class)
|
||||||
public abstract class ServerPlayNetworkHandlerMixin {
|
public class ServerPlayNetworkHandlerMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify quit message
|
||||||
|
*/
|
||||||
@ModifyArg(method = {"cleanUp"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
|
@ModifyArg(method = {"cleanUp"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
|
||||||
private Text quitMessage(Text text) {
|
private Text quitMessage(Text text) {
|
||||||
if (ConfigManager.getConfig().quitMessageEnabled) {
|
if (ConfigManager.getConfig().quitMessageEnabled) {
|
||||||
|
@ -10,8 +10,11 @@ import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
|||||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||||
|
|
||||||
@Mixin(ServerPlayerEntity.class)
|
@Mixin(ServerPlayerEntity.class)
|
||||||
public abstract class ServerPlayerEntityMixin {
|
public class ServerPlayerEntityMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change player list name if enabled
|
||||||
|
*/
|
||||||
@Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true)
|
||||||
private void changePlayerListName(CallbackInfoReturnable<Text> cir) {
|
private void changePlayerListName(CallbackInfoReturnable<Text> cir) {
|
||||||
if (ConfigManager.getConfig().dimensionIndicatorEnabled) {
|
if (ConfigManager.getConfig().dimensionIndicatorEnabled) {
|
||||||
|
@ -12,6 +12,9 @@ import wtf.hak.survivalfabric.utils.PacketUtils;
|
|||||||
@Mixin(ServerWorld.class)
|
@Mixin(ServerWorld.class)
|
||||||
public class ServerWorldMixin {
|
public class ServerWorldMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update List Names if needed
|
||||||
|
*/
|
||||||
@Inject(method = "onDimensionChanged", at = {@At("HEAD")})
|
@Inject(method = "onDimensionChanged", at = {@At("HEAD")})
|
||||||
public void onDimensionChange(Entity entity, CallbackInfo ci) {
|
public void onDimensionChange(Entity entity, CallbackInfo ci) {
|
||||||
if (entity instanceof ServerPlayerEntity) {
|
if (entity instanceof ServerPlayerEntity) {
|
||||||
|
64
src/main/java/wtf/hak/survivalfabric/utils/MathUtils.java
Normal file
64
src/main/java/wtf/hak/survivalfabric/utils/MathUtils.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package wtf.hak.survivalfabric.utils;
|
||||||
|
|
||||||
|
public class MathUtils {
|
||||||
|
|
||||||
|
|
||||||
|
public static double clamp(double value, double min, double max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float clamp(float value, float min, float max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int clamp(int value, int min, int max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasSupportedOperator(String msg) {
|
||||||
|
return msg.contains("+") || msg.contains("-") || msg.contains("*") || msg.contains("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double evaluateExpression(String expression) {
|
||||||
|
return evaluate(expression.replaceAll("\\s", ""), new int[]{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double evaluate(String expr, int[] index) {
|
||||||
|
double value = parseTerm(expr, index);
|
||||||
|
while (index[0] < expr.length()) {
|
||||||
|
char op = expr.charAt(index[0]);
|
||||||
|
if (op != '+' && op != '-') break;
|
||||||
|
index[0]++;
|
||||||
|
double nextTerm = parseTerm(expr, index);
|
||||||
|
value = (op == '+') ? value + nextTerm : value - nextTerm;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double parseTerm(String expr, int[] index) {
|
||||||
|
double value = parseFactor(expr, index);
|
||||||
|
while (index[0] < expr.length()) {
|
||||||
|
char op = expr.charAt(index[0]);
|
||||||
|
if (op != '*' && op != '/') break;
|
||||||
|
index[0]++;
|
||||||
|
double nextFactor = parseFactor(expr, index);
|
||||||
|
value = (op == '*') ? value * nextFactor : value / nextFactor;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double parseFactor(String expr, int[] index) {
|
||||||
|
if (expr.charAt(index[0]) == '(') {
|
||||||
|
index[0]++;
|
||||||
|
double value = evaluate(expr, index);
|
||||||
|
index[0]++; // Skip closing ')'
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = index[0];
|
||||||
|
while (index[0] < expr.length() && (Character.isDigit(expr.charAt(index[0])) || expr.charAt(index[0]) == '.')) {
|
||||||
|
index[0]++;
|
||||||
|
}
|
||||||
|
return Double.parseDouble(expr.substring(start, index[0]));
|
||||||
|
}
|
||||||
|
}
|
45
src/main/java/wtf/hak/survivalfabric/utils/Scheduler.java
Normal file
45
src/main/java/wtf/hak/survivalfabric/utils/Scheduler.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package wtf.hak.survivalfabric.utils;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class Scheduler {
|
||||||
|
|
||||||
|
private static Scheduler INSTANCE;
|
||||||
|
|
||||||
|
private final Map<Runnable, Long> tasks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public Scheduler() {
|
||||||
|
ServerTickEvents.END_SERVER_TICK.register((server) -> {
|
||||||
|
for(Runnable task : tasks.keySet()) {
|
||||||
|
long delay = tasks.get(task);
|
||||||
|
if(delay <= 0) {
|
||||||
|
task.run();
|
||||||
|
tasks.remove(task);
|
||||||
|
} else {
|
||||||
|
tasks.put(task, delay-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scheduleTask(Runnable task) {
|
||||||
|
scheduleTask(task, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scheduleTask(Runnable task, long delay) {
|
||||||
|
tasks.put(task, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Scheduler get() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Scheduler initialize() {
|
||||||
|
INSTANCE = new Scheduler();
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
@ -17,5 +17,6 @@
|
|||||||
"key.survivalfabric.angle14": "204.98 / -41.68",
|
"key.survivalfabric.angle14": "204.98 / -41.68",
|
||||||
"key.survivalfabric.angle15": "244.97 / -41.71",
|
"key.survivalfabric.angle15": "244.97 / -41.71",
|
||||||
"category.survivalfabric.survivalfabric": "Survival Fabric",
|
"category.survivalfabric.survivalfabric": "Survival Fabric",
|
||||||
"key.survivalfabric.camera": "/camera"
|
"key.survivalfabric.camera": "/camera",
|
||||||
|
"key.survivalfabric.zoom": "Zoom"
|
||||||
}
|
}
|
@ -1,48 +1,48 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"id": "survivalfabric",
|
"id": "survivalfabric",
|
||||||
"version": "${version}",
|
"version": "${version}",
|
||||||
"name": "SurvivalFabric",
|
"name": "SurvivalFabric",
|
||||||
"description": "Adds a few QOL features to your Survival!",
|
"description": "Adds a few QOL features to your Survival!",
|
||||||
"authors": [
|
"authors": [
|
||||||
"AlwaysHAK"
|
"AlwaysHAK"
|
||||||
],
|
],
|
||||||
"contact": {
|
"contact": {
|
||||||
"homepage": "https://hak.wtf",
|
"homepage": "https://hak.wtf",
|
||||||
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric",
|
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric",
|
||||||
"issues": "https://git.hak.wtf/hkuijlman/SurvivalFabric/issues"
|
"issues": "https://git.hak.wtf/hkuijlman/SurvivalFabric/issues"
|
||||||
},
|
},
|
||||||
"license": "CC0-1.0",
|
"license": "CC0-1.0",
|
||||||
"icon": "assets/survivalfabric/icon.png",
|
"icon": "assets/survivalfabric/icon.png",
|
||||||
"environment": "*",
|
"environment": "*",
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"main": [
|
"main": [
|
||||||
"wtf.hak.survivalfabric.SurvivalFabric"
|
"wtf.hak.survivalfabric.SurvivalFabric"
|
||||||
],
|
],
|
||||||
"client": [
|
"client": [
|
||||||
"wtf.hak.survivalfabric.SurvivalFabricClient"
|
"wtf.hak.survivalfabric.SurvivalFabricClient"
|
||||||
],
|
],
|
||||||
"fabric-datagen": [
|
"fabric-datagen": [
|
||||||
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
|
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
|
||||||
],
|
],
|
||||||
"modmenu": [
|
"modmenu": [
|
||||||
"wtf.hak.survivalfabric.modmenu.ModMenuIntegration"
|
"wtf.hak.survivalfabric.modmenu.ModMenuIntegration"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"survivalfabric.mixins.json",
|
"survivalfabric.mixins.json",
|
||||||
{
|
{
|
||||||
"config": "survivalfabric.client.mixins.json",
|
"config": "survivalfabric.client.mixins.json",
|
||||||
"environment": "client"
|
"environment": "client"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.16.10",
|
"fabricloader": ">=0.16.10",
|
||||||
"minecraft": "~1.21.5",
|
"minecraft": "~1.21.4",
|
||||||
"java": ">=21",
|
"java": ">=21",
|
||||||
"fabric-api": "*"
|
"fabric-api": "*"
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
"modmenu": "*"
|
"modmenu": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@
|
|||||||
"package": "wtf.hak.survivalfabric.mixin",
|
"package": "wtf.hak.survivalfabric.mixin",
|
||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "JAVA_21",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
|
"BlockMixin",
|
||||||
"PlayerManagerMixin",
|
"PlayerManagerMixin",
|
||||||
"ServerPlayerEntityMixin",
|
"ServerPlayerEntityMixin",
|
||||||
"ServerPlayNetworkHandlerMixin",
|
"ServerPlayNetworkHandlerMixin",
|
||||||
|
Reference in New Issue
Block a user