75 Commits

Author SHA1 Message Date
92e9c996b9 Updated README for 1.4.1 release
All checks were successful
build / build (push) Successful in 1m17s
2025-05-20 12:20:49 +02:00
dd50ff0376 Fixed BlockMixin for 1.21.4
All checks were successful
build / build (push) Successful in 1m16s
2025-05-20 12:15:16 +02:00
d2fd497f26 Merge branch 'master' into 1.21.4 2025-05-20 12:10:17 +02:00
a6f8645c68 Added toggleable scrollToZoom
All checks were successful
build / build (push) Successful in 1m13s
2025-05-20 12:01:59 +02:00
2be7c16e8b Refactored SFKeyBindings to CameraShortcut 2025-05-20 11:59:51 +02:00
ccbb15f7c4 Fixed bug in Replenish feature
All checks were successful
build / build (push) Successful in 1m43s
2025-05-19 22:23:08 +02:00
05a93b5ab0 Updated README.md
All checks were successful
build / build (push) Successful in 1m14s
2025-05-19 20:36:47 +02:00
327c92a81c Updated README.md 2025-05-19 20:32:24 +02:00
29067365ca Added Zoom functionality 2025-05-19 20:32:00 +02:00
1ae193ada7 Greatly improved Vein Miner performance 2025-05-19 20:31:13 +02:00
151fd022c4 Cleaned up code
All checks were successful
build / build (push) Successful in 1m25s
2025-05-19 12:06:39 +02:00
deb338f2c8 Added configurable Vein Miner animation 2025-05-19 11:59:39 +02:00
61d2a0b137 Updated README.md 2025-05-19 10:20:58 +02:00
35148c2159 Added Replenish function (replanting crops) 2025-05-19 10:20:47 +02:00
ef29652e07 Made leaves veinmineable using shears 2025-05-19 10:15:29 +02:00
ee448ae34d Removed debug message
All checks were successful
build / build (push) Successful in 1m11s
2025-04-27 16:36:36 +02:00
69df0e5850 Cleaned up some code 2025-04-27 15:41:38 +02:00
4988a91e70 Fixed mixin class types 2025-04-27 15:37:14 +02:00
8808df1cf1 Added zoom functionality
All checks were successful
build / build (push) Successful in 1m39s
2025-04-23 22:32:18 +02:00
963002570d Added zoom functionality 2025-04-23 22:32:13 +02:00
2c95b5e374 Oops 2025-04-23 21:26:56 +02:00
1d3dba1b02 Changed Mixin from interface(?) to abstract class 2025-04-23 21:21:10 +02:00
c7ea933494 Added documentation to Mixin classes 2025-04-23 20:53:15 +02:00
be6472cc79 Updated README.md
All checks were successful
build / build (push) Successful in 1m19s
2025-04-22 16:34:35 +02:00
49ee0c503f Fixed Chat Calc 2025-04-22 16:34:22 +02:00
f994e8cc11 Changed default config 2025-04-22 16:33:58 +02:00
8498a77735 Fixed toggleability for Vein Miner
All checks were successful
build / build (push) Successful in 1m14s
2025-04-13 17:51:47 +02:00
0dd5907a29 Updated mod version 2025-04-12 23:49:27 +02:00
c25790902c Revamped Config Screen 2025-04-12 23:48:47 +02:00
f51bde3d6e Render Block Entities from further away 2025-04-12 20:53:52 +02:00
563febe36f Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m16s
# Conflicts:
#	src/main/resources/fabric.mod.json
2025-04-12 17:31:34 +02:00
ba6153fa41 Updated README.md
All checks were successful
build / build (push) Successful in 1m15s
2025-04-12 17:29:40 +02:00
798027a3af Updated mod version 2025-04-12 17:19:40 +02:00
6b2d080746 Made teleport head lock toggleable 2025-04-12 16:59:39 +02:00
87b6900ff3 Cleaned up 2025-04-12 16:59:27 +02:00
c39249e3c1 Optimized chat message system 2025-04-12 16:31:45 +02:00
281297bdca Cleaned up
All checks were successful
build / build (push) Successful in 1m13s
2025-04-12 16:21:27 +02:00
55e9b0fafd Added /camera keybind 2025-04-12 16:19:59 +02:00
4b5b697883 Added color to Chat Calc answer & made optional to add an equals sign 2025-04-12 16:19:36 +02:00
9325e24c5c Removed unnecessary comment
All checks were successful
build / build (push) Successful in 1m16s
2025-04-12 15:21:21 +02:00
02d2f624cf Code cleanup 2025-04-11 17:30:08 +02:00
9041bacf48 Updated README.md 2025-04-11 17:21:59 +02:00
23aefb2f8d Code cleanup 2025-04-11 17:21:51 +02:00
0941d3929e Renamed Utils.java to PacketUtils.java 2025-04-11 14:18:38 +02:00
c7722115ed Fixed bug where EC closes if another player is still inside 2025-04-11 14:17:14 +02:00
168f916baa Moved features to "features" package 2025-04-11 14:07:19 +02:00
f28159b442 Open/close EC block while opening/closing SEC + Play open & close sounds 2025-04-11 14:06:21 +02:00
c3a13c8063 Optimized fog removal ever so slightly 2025-04-11 13:21:46 +02:00
6b806574ac Removed Darkness Effect (toggleable)
All checks were successful
build / build (push) Successful in 1m19s
2025-04-11 13:17:19 +02:00
ddd5b9bf75 Fixed typo
All checks were successful
build / build (push) Successful in 1m12s
2025-04-07 10:37:44 +02:00
38a244b023 Added correct ModMenu version for 1.21.4
All checks were successful
build / build (push) Successful in 1m21s
2025-04-07 10:37:17 +02:00
868e71eb10 Fixed typo 2025-04-07 10:37:07 +02:00
2a106c9a03 Merge branch 'master' into 1.21.4
# Conflicts:
#	gradle.properties
2025-04-07 10:33:20 +02:00
8c3798d456 Created basic Mod Menu Integration and made different fog types toggleable 2025-04-07 10:32:39 +02:00
d99467d953 Added ModMenu as (optional) dependency 2025-04-07 09:18:14 +02:00
1d0f7f0f05 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m9s
2025-04-06 12:25:41 +02:00
7d969d0013 Removed Game Fog (cleaned up AngleViewer code)
All checks were successful
build / build (push) Successful in 1m2s
2025-04-06 12:25:30 +02:00
52a97dc2c1 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m11s
2025-03-30 23:33:15 +02:00
0fede8adbf Added Chat Calculator
All checks were successful
build / build (push) Successful in 1m7s
2025-03-30 23:32:43 +02:00
5916cc5cc2 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m2s
2025-03-30 18:16:16 +02:00
31afdab2cf Reverted head movement unlock on teleport due to bugs
All checks were successful
build / build (push) Successful in 1m6s
revert Made head movement lock expire as soon as teleported
2025-03-30 18:15:18 +02:00
4e58bede4b Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m5s
2025-03-30 17:58:58 +02:00
7db6d01869 Made head movement lock expire as soon as teleported
All checks were successful
build / build (push) Successful in 1m2s
2025-03-30 17:57:57 +02:00
130b2db727 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:28:42 +02:00
16027c04d1 Made head movement locked while "angle viewing"
All checks were successful
build / build (push) Successful in 1m6s
2025-03-30 17:26:55 +02:00
c993621bdb Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m7s
2025-03-30 17:13:15 +02:00
8ae9a4f7c4 Fixed annoyance 2025-03-30 17:12:27 +02:00
b89ab99928 Downgraded mod to 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:06:42 +02:00
cae8c34759 Added Teleportation Angle Viewer
All checks were successful
build / build (push) Successful in 1m8s
(Also removed FPS counter)
2025-03-30 16:16:51 +02:00
77ae4cc1f0 Removed Magma Blocks and added Quartz Ore to veinmine 2025-03-30 14:45:44 +02:00
e465963daa Created 'build.yml' workflow
All checks were successful
build / build (push) Successful in 4m0s
2025-03-27 13:19:22 +01:00
6b862dfbc3 Deleted duplicate class 2025-03-27 11:31:52 +01:00
045623a67b Split client & common sources + POC FPS counter 2025-03-27 11:03:35 +01:00
b6bb6565a8 Updated mod to 1.21.5 2025-03-26 16:50:38 +01:00
8cebb278b8 Updated mod to 1.21.5 2025-03-26 16:45:19 +01:00
51 changed files with 1838 additions and 537 deletions

View File

@ -0,0 +1,32 @@
name: build
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v4
- name: Setup JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'microsoft'
- name: Make Gradle wrapper executable
run: chmod +x ./gradlew
- name: Build
run: ./gradlew build
- name: Capture build artifacts
uses: actions/upload-artifact@v3
with:
name: Artifacts
path: build/libs/

View File

@ -3,40 +3,81 @@
# ![Logo](https://i.imgur.com/ktkHND1.png) # ![Logo](https://i.imgur.com/ktkHND1.png)
This mod is FAR FROM FINISHED, and initially just created for our private Survival Server. This mod is FAR FROM FINISHED, and initially just created for our private Survival Server.
As a challenge I'm trying to make it as user-friendly as possible. (It ain't there yet tho ;) ) As a challenge I'm trying to make it as user-friendly as possible.
## Current feature-set # Current feature-set
## Server Side
### Features
- Custom join message - Custom join message
![Join Message](https://i.imgur.com/7uv5lUb.png) ![Join Message](https://i.imgur.com/7uv5lUb.png)
- Custom quit message - Custom quit message
![Quit Message](https://i.imgur.com/OhFq1BT.png) ![Quit Message](https://i.imgur.com/OhFq1BT.png)
- Custom chat message - Custom chat message
![Chat Message](https://i.imgur.com/PDwRywP.png) ![Chat Message](https://i.imgur.com/PDwRywP.png)
- Tab list dimension indicator - Tab list dimension indicator
![Overworld](https://i.imgur.com/FB1Y7gD.png) ![Overworld](https://i.imgur.com/FB1Y7gD.png)
![Nether](https://i.imgur.com/XxJDL7u.png) ![Nether](https://i.imgur.com/XxJDL7u.png)
![End](https://i.imgur.com/t5u9goh.png) ![End](https://i.imgur.com/t5u9goh.png)
![Spectator](https://i.imgur.com/eEn4V9S.png) ![Spectator](https://i.imgur.com/eEn4V9S.png)
- Config - Config
- Configurable messages - Configurable messages
- Feature toggle - Feature toggle
- Version "control" - Version "control"
- Shared Ender Chest - Shared Ender Chest
- Shared EC Access control (via config) - Shared EC Access control (via config)
- Open/close EC block while opening/closing SEC
- Play open & close sounds
- Vein miner - Vein miner
![VeinMiner](https://i.imgur.com/zOXWMNa.gif) - Configurable animation (tick delay)
- Leaves veinmineable using shears
Code inspired by Inferis!
![VeinMiner](https://i.imgur.com/zOXWMNa.gif)
- 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.
- /slimechunk (/sc) | See if you're currently in a slimechunk - /slimechunk (/sc) | See if you're currently in a slimechunk
### Misc ## Client Side
- Updated icon
## Features to come - Teleportation Angle Viewer for [this machine](https://www.youtube.com/watch?v=FnUE-ZaALLw)
- Toggleable pitch/yaw lock while teleporting
![Teleportation Keybindings](https://i.imgur.com/gjO1H3d.png)
- Remove game fog (lava, water, etc.)
- All types individually toggleable
- Remove darkness effect
- Toggleable
- 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
## General
- Rework config system
- Store server settings in world folder for better singleplayer use
## Client side
## Server Side
- Telekinesis - Telekinesis
Other than that no more features! Other than that no more features!

View File

@ -11,11 +11,22 @@ base {
} }
repositories { repositories {
// Add repositories to retrieve artifacts from in here. maven {
// You should only use this when depending on other mods because name = "Terraformers"
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically. url = "https://maven.terraformersmc.com/"
// See https://docs.gradle.org/current/userguide/declaring_repositories.html }
// for more information about repositories. }
loom {
splitEnvironmentSourceSets()
mods {
"survivalfabric" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
} }
fabricApi { fabricApi {
@ -26,13 +37,13 @@ fabricApi {
dependencies { dependencies {
// To change the versions see the gradle.properties file // To change the versions see the gradle.properties file
minecraft "net.minecraft:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// 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("com.terraformersmc:modmenu:${project.modmenu_version}")
} }
processResources { processResources {

View File

@ -9,9 +9,11 @@ yarn_mappings=1.21.4+build.8
loader_version=0.16.10 loader_version=0.16.10
# Mod Properties # Mod Properties
mod_version=1.3.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.0+1.21.4 fabric_version=0.119.0+1.21.4
modmenu_version=13.0.3

0
gradlew vendored Normal file → Executable file
View File

View File

@ -0,0 +1,24 @@
package wtf.hak.survivalfabric;
import net.fabricmc.api.ClientModInitializer;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
import wtf.hak.survivalfabric.features.AngleViewer;
import wtf.hak.survivalfabric.features.RemoveDarknessEffect;
import wtf.hak.survivalfabric.features.CameraShortcut;
import wtf.hak.survivalfabric.features.Zoom;
public class SurvivalFabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// Config
ClientConfigManager.getConfig();
// Features
AngleViewer.register();
RemoveDarknessEffect.register();
CameraShortcut.register();
Zoom.register();
}
}

View File

@ -4,8 +4,8 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
public class SurvivalFabricDataGenerator implements DataGeneratorEntrypoint { public class SurvivalFabricDataGenerator implements DataGeneratorEntrypoint {
@Override @Override
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
} }
} }

View File

@ -0,0 +1,20 @@
package wtf.hak.survivalfabric.config.client;
public class ClientConfig {
public String configVersion = "1.1";
public boolean renderNetherFog = false;
public boolean renderOverworldFog = false;
public boolean renderEndFog = false;
public boolean renderLavaFog = false;
public boolean renderWaterFog = false;
public boolean renderSnowFog = false;
public boolean removeDarknessEffect = 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;
}

View File

@ -0,0 +1,55 @@
package wtf.hak.survivalfabric.config.client;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.fabricmc.loader.api.FabricLoader;
import wtf.hak.survivalfabric.config.Config;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class ClientConfigManager {
private static final File CONFIG_FILE = FabricLoader.getInstance().getConfigDir().resolve("survivalfabric-client.json").toFile();
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static ClientConfig INSTANCE;
public static ClientConfig getConfig() {
if (INSTANCE == null) {
return load();
} else
return INSTANCE;
}
public static ClientConfig load() {
try (FileReader reader = new FileReader(CONFIG_FILE)) {
INSTANCE = GSON.fromJson(reader, ClientConfig.class);
if (INSTANCE.configVersion.equalsIgnoreCase(new Config().configVersion)) {
return INSTANCE;
}
INSTANCE.configVersion = new ClientConfig().configVersion;
save(INSTANCE);
return INSTANCE;
} catch (IOException e) {
ClientConfig config = new ClientConfig();
INSTANCE = config;
save(config);
return config;
}
}
public static void save() {
save(INSTANCE);
}
public static void save(ClientConfig config) {
try (FileWriter writer = new FileWriter(CONFIG_FILE)) {
GSON.toJson(config, writer);
} catch (IOException e) {
System.out.println("Error saving config: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,75 @@
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.network.ClientPlayerEntity;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AngleViewer {
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public static boolean PREVENT_HEAD_MOVEMENT = false;
public static void register() {
for (Angle angle : Angle.values()) {
KeyBinding keyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.survivalfabric." + angle.name().toLowerCase(),
InputUtil.Type.KEYSYM,
GLFW.GLFW_DONT_CARE,
"category.survivalfabric.tpangles"
));
ClientTickEvents.END_CLIENT_TICK.register(mc -> {
while (keyBinding.wasPressed()) {
ClientPlayerEntity player = mc.player;
if (player == null) return;
player.setYaw(angle.yaw);
player.setPitch(angle.pitch);
PREVENT_HEAD_MOVEMENT = true;
scheduler.schedule(() -> {
if (player == null) return;
PREVENT_HEAD_MOVEMENT = false;
player.setPitch(-90);
PREVENT_HEAD_MOVEMENT = true;
scheduler.schedule(() -> PREVENT_HEAD_MOVEMENT = false, 1500, TimeUnit.MILLISECONDS);
}, 1500, TimeUnit.MILLISECONDS);
}
});
}
}
public enum Angle {
ANGLE0(-65.19f, -54.23f),
ANGLE1(-24.86f, -54.23f),
ANGLE2(-65.02f, -41.68f),
ANGLE3(-25.03f, -41.71f),
ANGLE4(24.81f, -54.23f),
ANGLE5(65.14f, -54.23f),
ANGLE6(24.98f, -41.68f),
ANGLE7(64.97f, -41.71f),
ANGLE8(114.81f, -54.23f),
ANGLE9(155.14f, -54.23f),
ANGLE10(114.98f, -41.68f),
ANGLE11(154.97f, -41.71f),
ANGLE12(204.81f, -54.23f),
ANGLE13(245.14f, -54.23f),
ANGLE14(204.98f, -41.68f),
ANGLE15(244.97f, -41.71f);
public final float yaw;
public final float pitch;
Angle(float yaw, float pitch) {
this.yaw = yaw;
this.pitch = pitch;
}
}
}

View File

@ -0,0 +1,22 @@
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.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
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"));
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (client.player != null) {
if (CAMERA_BIND.wasPressed()) {
client.player.networkHandler.sendChatCommand("camera");
}
}
});
}
}

View File

@ -0,0 +1,19 @@
package wtf.hak.survivalfabric.features;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.effect.StatusEffects;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
public class RemoveDarknessEffect {
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (client.player != null && ClientConfigManager.getConfig().removeDarknessEffect) {
StatusEffectInstance darknessEffect = client.player.getStatusEffect(StatusEffects.DARKNESS);
if (darknessEffect != null)
client.player.removeStatusEffect(darknessEffect.getEffectType());
}
});
}
}

View 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);
}
}
}

View File

@ -0,0 +1,47 @@
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,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);
}
}

View File

@ -0,0 +1,32 @@
package wtf.hak.survivalfabric.mixin.client;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.entity.Entity;
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.config.client.ClientConfigManager;
import wtf.hak.survivalfabric.features.AngleViewer;
@Mixin(Entity.class)
public abstract class EntityMixin {
@Inject(method = "setYaw", at = @At("HEAD"), cancellable = true)
private void preventYawChange(float yaw, CallbackInfo ci) {
if ((Object) this instanceof ClientPlayerEntity player) {
if (player.isMainPlayer() && AngleViewer.PREVENT_HEAD_MOVEMENT && ClientConfigManager.getConfig().lockTeleportHeadMovement) {
ci.cancel();
}
}
}
@Inject(method = "setPitch", at = @At("HEAD"), cancellable = true)
private void preventPitchChange(float pitch, CallbackInfo ci) {
if ((Object) this instanceof ClientPlayerEntity player) {
if (player.isMainPlayer() && AngleViewer.PREVENT_HEAD_MOVEMENT && ClientConfigManager.getConfig().lockTeleportHeadMovement) {
ci.cancel();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,599 @@
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

@ -0,0 +1,15 @@
package wtf.hak.survivalfabric.modmenu;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(EnvType.CLIENT)
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ConfigScreen::new;
}
}

View File

@ -0,0 +1,15 @@
{
"required": true,
"package": "wtf.hak.survivalfabric.mixin.client",
"compatibilityLevel": "JAVA_21",
"client": [
"BackgroundRendererMixin",
"BlockEntityRendererMixin",
"EntityMixin",
"GameRendererMixin",
"MouseMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -1,7 +1,6 @@
package wtf.hak.survivalfabric; package wtf.hak.survivalfabric;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
@ -11,37 +10,38 @@ import org.slf4j.LoggerFactory;
import wtf.hak.survivalfabric.commands.ReloadConfigCommand; import wtf.hak.survivalfabric.commands.ReloadConfigCommand;
import wtf.hak.survivalfabric.commands.SlimeChunkCommand; import wtf.hak.survivalfabric.commands.SlimeChunkCommand;
import wtf.hak.survivalfabric.commands.SpectatorCommand; import wtf.hak.survivalfabric.commands.SpectatorCommand;
import wtf.hak.survivalfabric.sharedenderchest.SharedEnderChest; import wtf.hak.survivalfabric.features.sharedenderchest.SharedEnderChest;
import wtf.hak.survivalfabric.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;
public class SurvivalFabric implements ModInitializer { public class SurvivalFabric implements ModInitializer {
public static final String MOD_ID = "survivalfabric"; public static final String MOD_ID = "survivalfabric";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override @Override
public void onInitialize() { public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, new String[] { "spectator", "s", "S", "camera", "c", "C", })); Scheduler.initialize();
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, new String[] { "reloadsurvivalconfig" })); CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, "spectator", "s", "S", "camera", "c", "C"));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, new String[] { "slimechunk", "sc" })); CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, "reloadsurvivalconfig"));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, "slimechunk", "sc"));
if(getConfig().sharedEnderChestEnabled) if (getConfig().sharedEnderChestEnabled)
new SharedEnderChest().onInitialize(); new SharedEnderChest().onInitialize();
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; }
} });
});
ServerEntityEvents.ENTITY_LOAD.register(VeinMinerEvents::onEntityLoad); ServerEntityEvents.ENTITY_LOAD.register(VeinMinerEvents::onEntityLoad);
} }
} }
} }

View File

@ -6,11 +6,9 @@ import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.math.random.CheckedRandom;
import net.minecraft.util.math.random.ChunkRandom; import net.minecraft.util.math.random.ChunkRandom;
import net.minecraft.util.math.random.Random; import net.minecraft.util.math.random.Random;
import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.Chunk;
import wtf.hak.survivalfabric.config.ConfigManager;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig; import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
@ -31,7 +29,7 @@ public class SlimeChunkCommand {
ServerPlayerEntity p = (ServerPlayerEntity) source.getEntity(); ServerPlayerEntity p = (ServerPlayerEntity) source.getEntity();
Chunk chunk = p.getServerWorld().getChunk(p.getBlockPos()); Chunk chunk = p.getServerWorld().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.getServerWorld().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
p.sendMessage(Text.literal(getConfig().notInSlimeChunkMessage)); p.sendMessage(Text.literal(getConfig().notInSlimeChunkMessage));

View File

@ -8,7 +8,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.world.GameMode; import net.minecraft.world.GameMode;
import wtf.hak.survivalfabric.utils.Utils; import wtf.hak.survivalfabric.utils.PacketUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -36,7 +36,7 @@ public class SpectatorCommand {
player.teleport(data.world, data.x, data.y, data.z, Set.of(), data.yaw, data.pitch, false); player.teleport(data.world, data.x, data.y, data.z, Set.of(), data.yaw, data.pitch, false);
player.changeGameMode(GameMode.SURVIVAL); player.changeGameMode(GameMode.SURVIVAL);
spectating.remove(player); spectating.remove(player);
Utils.updateListNames(player); PacketUtils.updateListNames(player);
} else { } else {
spectating.put(player, new LocationData(player spectating.put(player, new LocationData(player
@ -47,7 +47,7 @@ public class SpectatorCommand {
.getPitch(), player .getPitch(), player
.getServerWorld())); .getServerWorld()));
player.changeGameMode(GameMode.SPECTATOR); player.changeGameMode(GameMode.SPECTATOR);
Utils.updateListNames(player); PacketUtils.updateListNames(player);
} }
return 1; return 1;
} }

View File

@ -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,14 +29,19 @@ 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 replenishEnabled = false;
public ScreenHandlerType<GenericContainerScreenHandler> screenHandlerType() { public ScreenHandlerType<GenericContainerScreenHandler> screenHandlerType() {
return switch (sharedEnderChestRows) { return switch (sharedEnderChestRows) {

View File

@ -24,7 +24,7 @@ public class ConfigManager {
} }
public static Config load() { public static Config load() {
try(FileReader reader = new FileReader(CONFIG_FILE)) { try (FileReader reader = new FileReader(CONFIG_FILE)) {
INSTANCE = GSON.fromJson(reader, Config.class); INSTANCE = GSON.fromJson(reader, Config.class);
if (INSTANCE.configVersion.equalsIgnoreCase(new Config().configVersion)) { if (INSTANCE.configVersion.equalsIgnoreCase(new Config().configVersion)) {
return INSTANCE; return INSTANCE;
@ -41,7 +41,7 @@ public class ConfigManager {
} }
public static void save(Config config) { public static void save(Config config) {
try(FileWriter writer = new FileWriter(CONFIG_FILE)) { try (FileWriter writer = new FileWriter(CONFIG_FILE)) {
GSON.toJson(config, writer); GSON.toJson(config, writer);
} catch (IOException e) { } catch (IOException e) {
System.out.println("Error saving config: " + e.getMessage()); System.out.println("Error saving config: " + e.getMessage());

View File

@ -1,15 +1,9 @@
package wtf.hak.survivalfabric.sharedenderchest; package wtf.hak.survivalfabric.features.sharedenderchest;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
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.inventory.Inventories;
@ -23,11 +17,14 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.*; import net.minecraft.util.ActionResult;
import net.minecraft.util.WorldSavePath;
import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.io.*;
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;
@ -37,6 +34,51 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
private long ticksUntilSave = -20; private long ticksUntilSave = -20;
public static void saveInventory(MinecraftServer server) {
File inventoryFile = getFile(server);
NbtCompound nbt = new NbtCompound();
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
Inventories.writeNbt(nbt, sharedInventory.getList(inventoryItemStacks), server.getRegistryManager());
try (FileOutputStream inventoryFileOutputStream = new FileOutputStream(inventoryFile);
DataOutputStream inventoryFileDataOutput = new DataOutputStream(inventoryFileOutputStream)) {
inventoryFile.createNewFile();
NbtIo.writeCompressed(nbt, inventoryFileDataOutput);
} catch (Exception e) {
LOGGER.error("Error while saving Shared Ender Chest: " + e);
}
}
public static void openSharedEnderChest(PlayerEntity player, World world, BlockPos pos) {
fakeEnderChestOpen(world, pos, true);
sharedInventory.openedEnderChests.put(player, pos);
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((int_1, playerInventory, playerEntity) ->
new GenericContainerScreenHandler(getConfig().screenHandlerType(), int_1, playerInventory, sharedInventory, getConfig().sharedEnderChestRows), Text.of(getConfig().sharedEnderChestName)));
}
public static void playEnderChestOpenSound(World world, BlockPos pos) {
world.playSound(null, pos, SoundEvents.BLOCK_ENDER_CHEST_OPEN, SoundCategory.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
}
public static void playEnderChestCloseSound(World world, BlockPos pos) {
world.playSound(null, pos, SoundEvents.BLOCK_ENDER_CHEST_CLOSE, SoundCategory.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
}
public static void fakeEnderChestOpen(World world, BlockPos pos, boolean open) {
if (!(world.getBlockState(pos).getBlock() instanceof EnderChestBlock)) {
return;
}
if (open)
playEnderChestOpenSound(world, pos);
else
playEnderChestCloseSound(world, pos);
world.addSyncedBlockEvent(pos, Blocks.ENDER_CHEST, 1, open ? 1 : 0);
}
private static File getFile(MinecraftServer server) {
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()) {
@ -55,19 +97,6 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
} }
} }
public static void saveInventory(MinecraftServer server) {
File inventoryFile = getFile(server);
NbtCompound nbt = new NbtCompound();
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
Inventories.writeNbt(nbt, sharedInventory.getList(inventoryItemStacks), server.getRegistryManager());
try (FileOutputStream inventoryFileOutputStream = new FileOutputStream(inventoryFile);
DataOutputStream inventoryFileDataOutput = new DataOutputStream(inventoryFileOutputStream)) {
inventoryFile.createNewFile();
NbtIo.writeCompressed(nbt, inventoryFileDataOutput);
} catch (Exception e) {
LOGGER.error("Error while saving Shared Ender Chest: " + e);
}
}
public void onServerStopping(MinecraftServer server) { public void onServerStopping(MinecraftServer server) {
saveInventory(server); saveInventory(server);
} }
@ -86,17 +115,15 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
if (world.getBlockState(hitResult.getBlockPos()).getBlock() instanceof EnderChestBlock) { if (world.getBlockState(hitResult.getBlockPos()).getBlock() instanceof EnderChestBlock) {
if (!player.isSpectator()) { if (!player.isSpectator()) {
if(!getConfig().sharedEnderChestLimitedAccess) { if (!getConfig().sharedEnderChestLimitedAccess) {
if (world.isClient()) return ActionResult.SUCCESS; if (world.isClient()) return ActionResult.SUCCESS;
playEnderChestOpenSound(world, hitResult.getBlockPos()); openSharedEnderChest(player, world, hitResult.getBlockPos());
openSharedEnderChest(player);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} else { } else {
for(String name : getConfig().sharedEnderChestNames) { for (String name : getConfig().sharedEnderChestNames) {
if(name.toLowerCase().strip().equalsIgnoreCase(player.getNameForScoreboard().toLowerCase())) { if (name.toLowerCase().strip().equalsIgnoreCase(player.getNameForScoreboard().toLowerCase())) {
if (world.isClient()) return ActionResult.SUCCESS; if (world.isClient()) return ActionResult.SUCCESS;
playEnderChestOpenSound(world, hitResult.getBlockPos()); openSharedEnderChest(player, world, hitResult.getBlockPos());
openSharedEnderChest(player);
return ActionResult.SUCCESS; return ActionResult.SUCCESS;
} }
} }
@ -113,17 +140,4 @@ public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, S
} }
public static void openSharedEnderChest(PlayerEntity player) {
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((int_1, playerInventory, playerEntity) ->
new GenericContainerScreenHandler(getConfig().screenHandlerType(), int_1, playerInventory, sharedInventory, getConfig().sharedEnderChestRows), Text.of(getConfig().sharedEnderChestName)));
}
public static void playEnderChestOpenSound(World world, BlockPos pos) {
world.playSound(null, pos, SoundEvents.BLOCK_ENDER_CHEST_OPEN, SoundCategory.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
}
private static File getFile(MinecraftServer server) {
return server.getSavePath(WorldSavePath.ROOT).resolve("sharedenderchest.sav").toFile();
}
} }

View File

@ -1,14 +1,18 @@
package wtf.hak.survivalfabric.sharedenderchest; package wtf.hak.survivalfabric.features.sharedenderchest;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories; import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory; import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import java.util.Iterator; import java.util.HashMap;
import java.util.Map;
public class SharedInventory implements Inventory { public class SharedInventory implements Inventory {
public final Map<PlayerEntity, BlockPos> openedEnderChests = new HashMap<>();
private final DefaultedList<ItemStack> stacks; private final DefaultedList<ItemStack> stacks;
public SharedInventory(int inventoryRows) { public SharedInventory(int inventoryRows) {
@ -19,28 +23,6 @@ public class SharedInventory implements Inventory {
this.stacks = dl; this.stacks = dl;
} }
/*
@Override
public void onClose(PlayerEntity player) {
Inventory.super.onClose(player);
EnderChestBlockEntity blockEntity = enderChests.remove(player);
if (blockEntity != null)
blockEntity.onClose(player);
}
@Override
public void onOpen(PlayerEntity player) {
Inventory.super.onOpen(player);
EnderChestBlockEntity blockEntity = enderChests.get(player);
if (blockEntity != null)
blockEntity.onOpen(player);
}
public void setBlockEntity(PlayerEntity player, EnderChestBlockEntity be) {
enderChests.put(player, be);
}
*/
public DefaultedList<ItemStack> getList(DefaultedList<ItemStack> dl) { public DefaultedList<ItemStack> getList(DefaultedList<ItemStack> dl) {
dl = stacks; dl = stacks;
@ -54,7 +36,7 @@ public class SharedInventory implements Inventory {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
Iterator<ItemStack> var1 = this.stacks.iterator(); var var1 = this.stacks.iterator();
ItemStack itemStack_1; ItemStack itemStack_1;
do { do {
@ -62,8 +44,8 @@ public class SharedInventory implements Inventory {
return true; return true;
} }
itemStack_1 = (ItemStack)var1.next(); itemStack_1 = var1.next();
} while(itemStack_1.isEmpty()); } while (itemStack_1.isEmpty());
return false; return false;
} }
@ -75,12 +57,7 @@ public class SharedInventory implements Inventory {
@Override @Override
public ItemStack removeStack(int int_1, int int_2) { public ItemStack removeStack(int int_1, int int_2) {
ItemStack itemStack_1 = Inventories.splitStack(this.stacks, int_1, int_2); return Inventories.splitStack(this.stacks, int_1, int_2);
if (!itemStack_1.isEmpty()) {
//this.container.onContentChanged(this);
}
return itemStack_1;
} }
@Override @Override
@ -91,7 +68,6 @@ public class SharedInventory implements Inventory {
@Override @Override
public void setStack(int i, ItemStack itemStack) { public void setStack(int i, ItemStack itemStack) {
this.stacks.set(i, itemStack); this.stacks.set(i, itemStack);
//this.container.onContentChanged(this);
} }
@Override @Override
@ -108,4 +84,12 @@ public class SharedInventory implements Inventory {
public void clear() { public void clear() {
stacks.clear(); stacks.clear();
} }
@Override
public void onClose(PlayerEntity player) {
BlockPos pos = openedEnderChests.remove(player);
if (openedEnderChests.containsValue(pos))
return;
SharedEnderChest.fakeEnderChestOpen(player.getWorld(), pos, false);
}
} }

View File

@ -1,10 +1,12 @@
package wtf.hak.survivalfabric.veinminer; package wtf.hak.survivalfabric.features.veinminer;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
public interface Drill { public interface Drill {
boolean canHandle(BlockState blockState); boolean canHandle(BlockState blockState);
boolean isRightTool(BlockPos pos); boolean isRightTool(BlockPos pos);
boolean drill(BlockPos blockPos); boolean drill(BlockPos blockPos);
} }

View File

@ -1,15 +1,14 @@
package wtf.hak.survivalfabric.veinminer; package wtf.hak.survivalfabric.features.veinminer;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import wtf.hak.survivalfabric.veinminer.drills.LeavesDrill; import wtf.hak.survivalfabric.features.veinminer.drills.LeavesDrill;
import wtf.hak.survivalfabric.veinminer.drills.OreDrill; import wtf.hak.survivalfabric.features.veinminer.drills.OreDrill;
import wtf.hak.survivalfabric.veinminer.drills.WoodDrill; import wtf.hak.survivalfabric.features.veinminer.drills.WoodDrill;
public class VeinMinerEvents { public class VeinMinerEvents {
@ -21,26 +20,25 @@ public class VeinMinerEvents {
boolean isVeinMining = VeinMinerSession.sessionForPlayer(player) != null; boolean isVeinMining = VeinMinerSession.sessionForPlayer(player) != null;
boolean canVeinMine = player.isInSneakingPose(); boolean canVeinMine = player.isInSneakingPose();
if (canVeinMine && !isVeinMining) { if (canVeinMine && !isVeinMining) {
VeinMinerSession session = VeinMinerSession.start(player, (ServerWorld)world, pos); VeinMinerSession session = VeinMinerSession.start(player, (ServerWorld) world, pos);
boolean shouldContinue = !mine(session); boolean shouldContinue = !mine(session);
session.finish(); session.finish();
return shouldContinue; return shouldContinue;
} } else {
else {
return true; return true;
} }
} }
public static void onEntityLoad(Entity entity, ServerWorld world) { public static void onEntityLoad(Entity entity, ServerWorld world) {
BlockPos pos = entity.getBlockPos(); BlockPos pos = entity.getBlockPos();
VeinMinerSession session = VeinMinerSession.sessionForPosition(pos); VeinMinerSession session = VeinMinerSession.sessionForPosition(pos);
if (session != null) { if (session != null) {
entity.setPos(session.initialPos.getX(), session.initialPos.getY(), session.initialPos.getZ()); entity.setPos(session.initialPos.getX(), session.initialPos.getY(), session.initialPos.getZ());
} }
} }
private static boolean mine(VeinMinerSession session) { private static boolean mine(VeinMinerSession session) {
Drill[] drills = new Drill[] { Drill[] drills = new Drill[]{
new OreDrill(session), new OreDrill(session),
new WoodDrill(session), new WoodDrill(session),
new LeavesDrill(session) new LeavesDrill(session)

View File

@ -1,23 +1,31 @@
package wtf.hak.survivalfabric.veinminer; package wtf.hak.survivalfabric.features.veinminer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class VeinMinerSession { public class VeinMinerSession {
private static ArrayList<VeinMinerSession> sessions = new ArrayList<>(); private static final ArrayList<VeinMinerSession> sessions = new ArrayList<>();
public ServerPlayerEntity player; public ServerPlayerEntity player;
public ServerWorld world; public ServerWorld world;
public Set<BlockPos> positions; public Set<BlockPos> positions;
public BlockPos initialPos; public BlockPos initialPos;
private VeinMinerSession(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) {
this.player = player;
this.world = world;
this.initialPos = initialPos;
this.positions = new HashSet<>();
positions.add(initialPos);
}
public static VeinMinerSession sessionForPlayer(ServerPlayerEntity player) { public static VeinMinerSession sessionForPlayer(ServerPlayerEntity player) {
for (var session: sessions) { for (var session : sessions) {
if (session.player == player) { if (session.player == player) {
return session; return session;
} }
@ -26,7 +34,7 @@ public class VeinMinerSession {
} }
public static VeinMinerSession sessionForPosition(BlockPos position) { public static VeinMinerSession sessionForPosition(BlockPos position) {
for (var session: sessions) { for (var session : sessions) {
if (session.positions.contains(position)) { if (session.positions.contains(position)) {
return session; return session;
} }
@ -44,14 +52,6 @@ public class VeinMinerSession {
sessions.remove(session); sessions.remove(session);
} }
private VeinMinerSession(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) {
this.player = player;
this.world = world;
this.initialPos = initialPos;
this.positions = new HashSet<>();
positions.add(initialPos);
}
public void addPosition(BlockPos pos) { public void addPosition(BlockPos pos) {
positions.add(pos); positions.add(pos);
} }

View File

@ -0,0 +1,193 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.text.PlainTextContent;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.Drill;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import wtf.hak.survivalfabric.utils.Scheduler;
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.config.ConfigManager.getConfig;
public class DrillBase implements Drill {
protected VeinMinerSession session;
protected TagKey<Block> tag;
public DrillBase(VeinMinerSession session, TagKey<Block> tag) {
this.session = session;
this.tag = tag;
}
@Override
public boolean canHandle(BlockState blockState) {
return blockState.isIn(tag);
}
@Override
public boolean drill(BlockPos startPos) {
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
public boolean isRightTool(BlockPos pos) {
var blockState = session.world.getBlockState(pos);
return session.player.getMainHandStack().isSuitableFor(blockState);
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) {
forXYZ(pos, max, new ForXYZCounter() {
@Override
public int handle(BlockPos pos) {
handler.handle(pos);
return 1; // Accumulate one count for each position processed
}
}, false);
}
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler) {
return forXYZ(pos, max, handler, false);
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) {
forXYZ(pos, max, new ForXYZCounter() {
@Override
public int handle(BlockPos pos) {
handler.handle(pos);
return 1; // Accumulate one count for each position processed
}
}, forceVertical);
}
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler, boolean forceVertical) {
Set<Integer> offsets = new HashSet<>();
for (int d = 0; d <= max; ++d) {
offsets.add(d);
if (d != -d) {
offsets.add(-d);
}
}
String[] order = determineOrder(forceVertical);
int counter = 0;
for (int i1 : offsets) {
for (int i2 : offsets) {
for (int i3 : offsets) {
int ix = order[0].equals("x") ? i1 : order[1].equals("x") ? i2 : i3;
int iy = order[0].equals("y") ? i1 : order[1].equals("y") ? i2 : i3;
int iz = order[0].equals("z") ? i1 : order[1].equals("z") ? i2 : i3;
int px = pos.getX() + ix;
int py = pos.getY() + iy;
int pz = pos.getZ() + iz;
counter += handler.handle(new BlockPos(px, py, pz)); // Works with ForXYZCounter, returning an int
}
}
}
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) {
session.addPosition(blockPos);
boolean success = isRightTool(blockPos) && session.player.interactionManager.tryBreakBlock(blockPos);
if (!success) {
session.removePosition(blockPos);
}
return success;
}
protected void _log(String message) {
PlainTextContent literal = new PlainTextContent.Literal(message);
Text text = MutableText.of(literal);
session.player.sendMessage(text);
LOGGER.info(message);
}
protected interface ForXYZHandler {
void handle(BlockPos pos);
}
protected interface ForXYZCounter {
int handle(BlockPos pos);
}
}

View File

@ -0,0 +1,22 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.item.Items;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
public class LeavesDrill extends DrillBase {
public LeavesDrill(VeinMinerSession session) {
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves")));
}
@Override
public boolean isRightTool(BlockPos pos) {
var blockState = session.world.getBlockState(pos);
return session.player.getMainHandStack().isSuitableFor(blockState) || session.player.getMainHandStack().getItem() == Items.SHEARS;
}
}

View File

@ -0,0 +1,14 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
public class OreDrill extends DrillBase {
public OreDrill(VeinMinerSession session) {
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore")));
}
}

View File

@ -0,0 +1,14 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.util.Identifier;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
public class WoodDrill extends DrillBase {
public WoodDrill(VeinMinerSession session) {
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood")));
}
}

View 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;
}
}

View File

@ -15,45 +15,85 @@ 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.Objects;
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) { if (ConfigManager.getConfig().joinMessageEnabled && !player.getServer().isSingleplayer()) {
Text text = Text.literal(String.format(ConfigManager.getConfig().joinMessage, player.getName().getString())); Text text = Text.literal(String.format(ConfigManager.getConfig().joinMessage, player.getName().getString()));
player.sendMessage(text, false); player.sendMessage(text, false);
} }
} }
/**
* 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) {
String name = text.getString().split(" ")[0]; String name = text.getString().split(" ")[0];
return Text.literal(String.format(ConfigManager.getConfig().joinMessage, name)); return Text.literal(String.format(ConfigManager.getConfig().joinMessage, name));
} else } else
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)) {
SpectatorCommand.LocationData loc = SpectatorCommand.spectating.remove(player); SpectatorCommand.LocationData loc = SpectatorCommand.spectating.remove(player);
player.teleport(loc.world, loc.x,loc.y,loc.z, Set.of(), loc.yaw, loc.pitch, false); player.teleport(loc.world, loc.x, loc.y, loc.z, Set.of(), loc.yaw, loc.pitch, false);
player.changeGameMode(GameMode.SURVIVAL); player.changeGameMode(GameMode.SURVIVAL);
} }
} }
/**
* 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 && ConfigManager.getConfig().chatMessageEnabled) { if (sender != null) {
Text text = Text.literal(String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), message.getContent().getString())); String rawMessage = message.getContent().getString().trim();
Objects.requireNonNull(sender.getServer()).getPlayerManager().broadcast(text, false); boolean isCalcEnabled = ConfigManager.getConfig().chatCalcEnabled;
boolean isMsgEnabled = ConfigManager.getConfig().chatMessageEnabled;
String processedMessage = rawMessage;
if (isCalcEnabled && MathUtils.hasSupportedOperator(rawMessage)) {
String expression = rawMessage.endsWith("=") ? rawMessage.substring(0, rawMessage.length() - 1).trim() : rawMessage;
try {
String result = String.valueOf(MathUtils.evaluateExpression(expression));
StringBuilder sb = new StringBuilder(rawMessage).append("§6");
if (rawMessage.contains(" ")) sb.append(" ");
if (!rawMessage.endsWith("=")) sb.append("=");
if (rawMessage.contains(" ")) sb.append(" ");
sb.append(result.endsWith(".0") ? result.substring(0, result.length() - 2) : result);
processedMessage = sb.toString();
} catch (Exception ignored) {}
}
if (isMsgEnabled) {
String formatted = String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), processedMessage);
sender.getServer().getPlayerManager().broadcast(Text.literal(formatted), false);
} else if (isCalcEnabled) {
String formatted = "<" + sender.getName().getString() + "> " + processedMessage;
sender.getServer().getPlayerManager().broadcast(Text.literal(formatted), false);
}
ci.cancel(); ci.cancel();
} }
} }
} }

View File

@ -8,11 +8,14 @@ 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) {
String name = text.getString().split(" ")[0]; String name = text.getString().split(" ")[0];
return Text.literal(String.format(ConfigManager.getConfig().quitMessage, name)); return Text.literal(String.format(ConfigManager.getConfig().quitMessage, name));
} }

View File

@ -10,11 +10,14 @@ 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) {
ServerPlayerEntity p = (ServerPlayerEntity) (Object) this; ServerPlayerEntity p = (ServerPlayerEntity) (Object) this;
String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey(); String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey();
String finalName; String finalName;

View File

@ -7,15 +7,18 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import wtf.hak.survivalfabric.utils.Utils; 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) {
Utils.updateListNames((ServerPlayerEntity)entity); PacketUtils.updateListNames((ServerPlayerEntity) entity);
} }
} }
} }

View 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]));
}
}

View File

@ -5,10 +5,10 @@ import net.minecraft.server.network.ServerPlayerEntity;
import java.util.Objects; import java.util.Objects;
public class Utils { public class PacketUtils {
public static void updateListNames(ServerPlayerEntity p) { public static void updateListNames(ServerPlayerEntity p) {
for(ServerPlayerEntity sp : Objects.requireNonNull(p.getServer()).getPlayerManager().getPlayerList()) { for (ServerPlayerEntity sp : Objects.requireNonNull(p.getServer()).getPlayerManager().getPlayerList()) {
sp.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME, p)); sp.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME, p));
} }
} }

View 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;
}
}

View File

@ -1,133 +0,0 @@
package wtf.hak.survivalfabric.veinminer.drills;
import net.minecraft.block.BlockState;
import java.util.ArrayList;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.text.PlainTextContent;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.veinminer.Drill;
import wtf.hak.survivalfabric.veinminer.VeinMinerSession;
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
public class DrillBase implements Drill {
protected VeinMinerSession session;
public DrillBase(VeinMinerSession session) {
this.session = session;
}
@Override
public boolean canHandle(BlockState blockId) {
return false;
}
@Override
public boolean drill(BlockPos blockPos) {
return false;
}
@Override
public boolean isRightTool(BlockPos pos) {
var blockState = session.world.getBlockState(pos);
return session.player.getMainHandStack().isSuitableFor(blockState);
}
protected interface ForXYZHandler {
public void handle(BlockPos pos);
}
protected interface ForXYZCounter {
public int handle(BlockPos pos);
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) {
forXYZ(pos, max, handlerPos -> {
handler.handle(handlerPos);
return 0;
}, false);
}
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler) {
return forXYZ(pos, max, handler, false);
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) {
forXYZ(pos, max, handlerPos -> {
handler.handle(handlerPos);
return 0;
}, forceVertical);
}
protected int forXYZ(BlockPos pos, int max, ForXYZCounter handler, boolean forceVertical) {
ArrayList<Integer> offsets = new ArrayList<Integer>();
for (int d = 0; d <= max; ++d) {
offsets.add(d);
if (d != -d) {
offsets.add(-d);
}
}
String[] order = new String[] { "x", "y", "z" };
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;
for (int i1: offsets) {
for (int i2: offsets) {
for (int i3: offsets) {
int ix = order[0] == "x" ? i1 : order[1] == "x" ? i2 : i3;
int iy = order[0] == "y" ? i1 : order[1] == "y" ? i2 : i3;
int iz = order[0] == "z" ? i1 : order[1] == "z" ? i2 : i3;
int px = pos.getX() + ix;
int py = pos.getY() + iy;
int pz = pos.getZ() + iz;
counter += handler.handle(new BlockPos(px, py, pz));
}
}
}
return counter;
}
protected boolean tryBreakBlock(BlockPos blockPos) {
session.addPosition(blockPos);
boolean success = isRightTool(blockPos) && session.player.interactionManager.tryBreakBlock(blockPos);
if (!success) {
session.removePosition(blockPos);
}
return success;
}
protected void _log(String message) {
PlainTextContent literal = new PlainTextContent.Literal(message);
Text text = MutableText.of(literal);
session.player.sendMessage(text);
LOGGER.info(message);
}
}

View File

@ -1,62 +0,0 @@
package wtf.hak.survivalfabric.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class LeavesDrill extends DrillBase {
public LeavesDrill(VeinMinerSession session) {
super(session);
}
public static final TagKey<Block> leavesTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves"));
@Override
public boolean canHandle(BlockState blockState) {
return blockState.isIn(leavesTag);
}
@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;
}
}

View File

@ -1,64 +0,0 @@
package wtf.hak.survivalfabric.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import java.util.ArrayDeque;
import net.minecraft.registry.Registries;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.veinminer.VeinMinerSession;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class OreDrill extends DrillBase {
public OreDrill(VeinMinerSession session) {
super(session);
}
public static final TagKey<Block> oreTag = 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;
}
}

View File

@ -1,85 +0,0 @@
package wtf.hak.survivalfabric.veinminer.drills;
import net.minecraft.block.BlockState;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import java.util.ArrayDeque;
import net.minecraft.block.Block;
import net.minecraft.registry.Registries;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.veinminer.VeinMinerSession;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class WoodDrill extends DrillBase {
public WoodDrill(VeinMinerSession session) {
super(session);
}
public static final TagKey<Block> woodTag = 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<BlockPos>();
ArrayDeque<BlockPos> logBlocks = new ArrayDeque<BlockPos>();
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);
}
}
// second round, leaves
// The pending blocks are all air now,
ArrayDeque<BlockPos> pendingLeaves = logBlocks;
while (!pendingLeaves.isEmpty() && broken < getConfig().maxVeinSize) {
// remove the immediately surrounding leaves around the log blocks
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");
}
}

View File

@ -0,0 +1,22 @@
{
"category.survivalfabric.tpangles": "Teleportation Angles",
"key.survivalfabric.angle0": "-65.19 / -54.23",
"key.survivalfabric.angle1": "-24.86 / -54.23",
"key.survivalfabric.angle2": "-65.02 / -41.68",
"key.survivalfabric.angle3": "-25.03 / -41.71",
"key.survivalfabric.angle4": "24.81 / -54.23",
"key.survivalfabric.angle5": "65.14 / -54.23",
"key.survivalfabric.angle6": "24.98 / -41.68",
"key.survivalfabric.angle7": "64.97 / -41.71",
"key.survivalfabric.angle8": "114.81 / -54.23",
"key.survivalfabric.angle9": "155.14 / -54.23",
"key.survivalfabric.angle10": "114.98 / -41.68",
"key.survivalfabric.angle11": "154.97 / -41.71",
"key.survivalfabric.angle12": "204.81 / -54.23",
"key.survivalfabric.angle13": "245.14 / -54.23",
"key.survivalfabric.angle14": "204.98 / -41.68",
"key.survivalfabric.angle15": "244.97 / -41.71",
"category.survivalfabric.survivalfabric": "Survival Fabric",
"key.survivalfabric.camera": "/camera",
"key.survivalfabric.zoom": "Zoom"
}

View File

@ -9,6 +9,6 @@
"#minecraft:coal_ores", "#minecraft:coal_ores",
"#minecraft:copper_ores", "#minecraft:copper_ores",
"minecraft:ancient_debris", "minecraft:ancient_debris",
"minecraft:magma_block" "minecraft:nether_quartz_ore"
] ]
} }

View File

@ -9,7 +9,8 @@
], ],
"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"
}, },
"license": "CC0-1.0", "license": "CC0-1.0",
"icon": "assets/survivalfabric/icon.png", "icon": "assets/survivalfabric/icon.png",
@ -18,12 +19,22 @@
"main": [ "main": [
"wtf.hak.survivalfabric.SurvivalFabric" "wtf.hak.survivalfabric.SurvivalFabric"
], ],
"client": [
"wtf.hak.survivalfabric.SurvivalFabricClient"
],
"fabric-datagen": [ "fabric-datagen": [
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator" "wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
],
"modmenu": [
"wtf.hak.survivalfabric.modmenu.ModMenuIntegration"
] ]
}, },
"mixins": [ "mixins": [
"survivalfabric.mixins.json" "survivalfabric.mixins.json",
{
"config": "survivalfabric.client.mixins.json",
"environment": "client"
}
], ],
"depends": { "depends": {
"fabricloader": ">=0.16.10", "fabricloader": ">=0.16.10",
@ -31,7 +42,7 @@
"java": ">=21", "java": ">=21",
"fabric-api": "*" "fabric-api": "*"
}, },
"suggests": { "optional": {
"modmenu": "*"
} }
} }

View File

@ -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",
@ -10,5 +11,5 @@
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
} }
} }