Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
d675832ecb | |||
a6f8645c68 | |||
2be7c16e8b | |||
ccbb15f7c4 | |||
05a93b5ab0 | |||
327c92a81c | |||
29067365ca | |||
1ae193ada7 | |||
151fd022c4 | |||
deb338f2c8 | |||
61d2a0b137 | |||
35148c2159 | |||
ef29652e07 | |||
ee448ae34d | |||
69df0e5850 | |||
4988a91e70 | |||
8808df1cf1 | |||
963002570d | |||
2c95b5e374 | |||
1d3dba1b02 | |||
c7ea933494 | |||
be6472cc79 | |||
49ee0c503f | |||
f994e8cc11 | |||
8498a77735 | |||
0dd5907a29 | |||
c25790902c | |||
f51bde3d6e | |||
ba6153fa41 | |||
798027a3af | |||
6b2d080746 | |||
87b6900ff3 | |||
c39249e3c1 | |||
281297bdca | |||
55e9b0fafd | |||
4b5b697883 | |||
9325e24c5c | |||
02d2f624cf | |||
9041bacf48 | |||
23aefb2f8d | |||
0941d3929e | |||
c7722115ed | |||
168f916baa | |||
f28159b442 | |||
c3a13c8063 | |||
6b806574ac | |||
ddd5b9bf75 | |||
8c3798d456 | |||
d99467d953 | |||
7d969d0013 | |||
0fede8adbf | |||
31afdab2cf | |||
7db6d01869 | |||
16027c04d1 | |||
8ae9a4f7c4 | |||
cae8c34759 | |||
77ae4cc1f0 | |||
e465963daa | |||
6b862dfbc3 | |||
045623a67b | |||
b6bb6565a8 | |||
8cebb278b8 | |||
f17519a547 | |||
8be2f2dc31 | |||
091efb900a | |||
824c84e329 | |||
73b8be0930 | |||
645529a9f6 | |||
8856bec073 | |||
7ad7ce6ed4 | |||
78a56b6f28 | |||
6e15c54b80 | |||
22de60988f | |||
c8f907dc57 | |||
8377f36114 | |||
f3a1156fd4 | |||
5eae9d4a8c | |||
83cc3a38e9 | |||
7d4dca66f0 | |||
f6f0fc65d7 | |||
7b247ce2cc | |||
4894b7d389 |
32
.gitea/workflows/build.yml
Normal file
32
.gitea/workflows/build.yml
Normal 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/
|
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
@ -1,30 +0,0 @@
|
||||
# Automatically build the project and run any configured tests for every push
|
||||
# and submitted pull request. This can help catch issues that only occur on
|
||||
# certain platforms or Java versions, and provides a first line of defence
|
||||
# against bad commits.
|
||||
|
||||
name: build
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
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@v4
|
||||
with:
|
||||
name: Artifacts
|
||||
path: build/libs/
|
84
README.md
Normal file
84
README.md
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
# 
|
||||
|
||||
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.
|
||||
# Current feature-set
|
||||
|
||||
## Server Side
|
||||
|
||||
- Custom join message
|
||||

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

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

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

|
||||

|
||||

|
||||

|
||||
- Config
|
||||
- Configurable messages
|
||||
- Feature toggle
|
||||
- Version "control"
|
||||
- Shared Ender Chest
|
||||
- Shared EC Access control (via config)
|
||||
- Open/close EC block while opening/closing SEC
|
||||
- Play open & close sounds
|
||||
- Vein miner
|
||||
- Configurable animation (tick delay)
|
||||
- Leaves veinmineable using shears
|
||||
Code inspired by Inferis!
|
||||

|
||||
- Chat Calculator
|
||||
- Check if operator is present before calculating
|
||||
- Replenish
|
||||
|
||||
|
||||
### 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.
|
||||
- /slimechunk (/sc) | See if you're currently in a slimechunk
|
||||
|
||||
## Client Side
|
||||
|
||||
- Teleportation Angle Viewer for [this machine](https://www.youtube.com/watch?v=FnUE-ZaALLw)
|
||||
- Toggleable pitch/yaw lock while teleporting
|
||||

|
||||
- 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
|
||||
|
||||
Other than that no more features!
|
||||
Be sure to give your idea by [opening an issue](https://git.hak.wtf/hkuijlman/SurvivalFabric/issues/new) with the label Feature Request
|
25
build.gradle
25
build.gradle
@ -11,11 +11,22 @@ base {
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Add repositories to retrieve artifacts from in here.
|
||||
// You should only use this when depending on other mods because
|
||||
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
|
||||
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
|
||||
// for more information about repositories.
|
||||
maven {
|
||||
name = "Terraformers"
|
||||
url = "https://maven.terraformersmc.com/"
|
||||
}
|
||||
}
|
||||
|
||||
loom {
|
||||
splitEnvironmentSourceSets()
|
||||
|
||||
mods {
|
||||
"survivalfabric" {
|
||||
sourceSet sourceSets.main
|
||||
sourceSet sourceSets.client
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fabricApi {
|
||||
@ -26,13 +37,13 @@ fabricApi {
|
||||
|
||||
dependencies {
|
||||
// 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"
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
|
||||
// Fabric API. This is technically optional, but you probably want it anyway.
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
@ -4,14 +4,16 @@ org.gradle.parallel=true
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/develop
|
||||
minecraft_version=1.21.4
|
||||
yarn_mappings=1.21.4+build.8
|
||||
minecraft_version=1.21.5
|
||||
yarn_mappings=1.21.5+build.1
|
||||
loader_version=0.16.10
|
||||
|
||||
# Mod Properties
|
||||
mod_version=1.0.0
|
||||
mod_version=1.4.1
|
||||
maven_group=wtf.hak.survivalfabric
|
||||
archives_base_name=survivalfabric
|
||||
|
||||
# Dependencies
|
||||
fabric_version=0.119.0+1.21.4
|
||||
fabric_version=0.119.5+1.21.5
|
||||
|
||||
modmenu_version=14.0.0-rc.2
|
@ -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();
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
|
||||
public class SurvivalFabricDataGenerator implements DataGeneratorEntrypoint {
|
||||
@Override
|
||||
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
|
||||
@Override
|
||||
public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
63
src/client/java/wtf/hak/survivalfabric/features/Zoom.java
Normal file
63
src/client/java/wtf/hak/survivalfabric/features/Zoom.java
Normal file
@ -0,0 +1,63 @@
|
||||
package wtf.hak.survivalfabric.features;
|
||||
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.option.KeyBinding;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
import net.minecraft.text.Text;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
||||
|
||||
public class Zoom {
|
||||
|
||||
private static final KeyBinding ZOOM_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.zoom", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_C, "category.survivalfabric.survivalfabric"));
|
||||
|
||||
private static boolean SHOULD_ZOOM = false;
|
||||
private static int ZOOM_STEP = 0;
|
||||
|
||||
@Unique
|
||||
private static boolean initialSmoothZoom;
|
||||
|
||||
public static void register() {
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client -> {
|
||||
|
||||
if (ZOOM_BIND.isPressed() && !SHOULD_ZOOM) {
|
||||
if(getConfig().smoothCamera) {
|
||||
initialSmoothZoom = MinecraftClient.getInstance().options.smoothCameraEnabled;
|
||||
MinecraftClient.getInstance().options.smoothCameraEnabled = true;
|
||||
}
|
||||
SHOULD_ZOOM = true;
|
||||
} else if (!ZOOM_BIND.isPressed() && SHOULD_ZOOM) {
|
||||
SHOULD_ZOOM = false;
|
||||
ZOOM_STEP = 0;
|
||||
if(getConfig().smoothCamera) {
|
||||
MinecraftClient.getInstance().options.smoothCameraEnabled = initialSmoothZoom;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isZooming() {
|
||||
return SHOULD_ZOOM;
|
||||
}
|
||||
|
||||
public static float getZoomFov() {
|
||||
return getConfig().initialZoom - -ZOOM_STEP * getConfig().zoomStep;
|
||||
}
|
||||
|
||||
public static void modifyStep(int step) {
|
||||
ZOOM_STEP += step;
|
||||
|
||||
// Clamp the zoom level so the FOV stays within [1, 110]
|
||||
float zoomFov = getZoomFov();
|
||||
if (zoomFov < 1) {
|
||||
ZOOM_STEP = Math.round((1 - getConfig().initialZoom) / getConfig().zoomStep) + 1;
|
||||
} else if (zoomFov > 110) {
|
||||
ZOOM_STEP = Math.round((110 - getConfig().initialZoom) / getConfig().zoomStep);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package wtf.hak.survivalfabric.mixin.client;
|
||||
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
|
||||
|
||||
@Mixin(BlockEntityRenderer.class)
|
||||
public interface BlockEntityRendererMixin<T extends BlockEntity> {
|
||||
|
||||
@Inject(method = "getRenderDistance", at = @At("HEAD"), cancellable = true)
|
||||
private void getRenderDistance(CallbackInfoReturnable<Integer> cir) {
|
||||
if (ClientConfigManager.getConfig().manipulateBlockEntityDistance)
|
||||
cir.setReturnValue(ClientConfigManager.getConfig().blockEntityRange);
|
||||
}
|
||||
}
|
@ -0,0 +1,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package wtf.hak.survivalfabric.mixin.client;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.client.render.GameRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import wtf.hak.survivalfabric.features.Zoom;
|
||||
|
||||
@Mixin(GameRenderer.class)
|
||||
public class GameRendererMixin {
|
||||
|
||||
/**
|
||||
* Modify Zoom FOV
|
||||
*/
|
||||
@ModifyReturnValue(method = "getFov", at = @At("RETURN"))
|
||||
private float modifyFovWithZoom(float fov, @Local(argsOnly = true) float tickDelta) {
|
||||
return Zoom.isZooming() ? Zoom.getZoomFov() : fov;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package wtf.hak.survivalfabric.mixin.client;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.Mouse;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import wtf.hak.survivalfabric.features.Zoom;
|
||||
|
||||
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
|
||||
|
||||
@Mixin(Mouse.class)
|
||||
public class MouseMixin {
|
||||
|
||||
/**
|
||||
* Scrolling listener for zooming
|
||||
*/
|
||||
@Inject(method = "onMouseScroll", at = @At("HEAD"), cancellable = true)
|
||||
private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) {
|
||||
if (Zoom.isZooming() && getConfig().scrollToZoom) {
|
||||
if (MinecraftClient.getInstance().player != null) {
|
||||
if (vertical > 0)
|
||||
Zoom.modifyStep(-1);
|
||||
else
|
||||
Zoom.modifyStep(1);
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
599
src/client/java/wtf/hak/survivalfabric/modmenu/ConfigScreen.java
Normal file
599
src/client/java/wtf/hak/survivalfabric/modmenu/ConfigScreen.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
15
src/client/resources/survivalfabric.client.mixins.json
Normal file
15
src/client/resources/survivalfabric.client.mixins.json
Normal 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
|
||||
}
|
||||
}
|
@ -1,19 +1,47 @@
|
||||
package wtf.hak.survivalfabric;
|
||||
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
|
||||
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import wtf.hak.survivalfabric.commands.ReloadConfigCommand;
|
||||
import wtf.hak.survivalfabric.commands.SlimeChunkCommand;
|
||||
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
||||
import wtf.hak.survivalfabric.features.sharedenderchest.SharedEnderChest;
|
||||
import wtf.hak.survivalfabric.features.veinminer.VeinMinerEvents;
|
||||
import wtf.hak.survivalfabric.utils.Scheduler;
|
||||
|
||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
||||
|
||||
public class SurvivalFabric implements ModInitializer {
|
||||
public static final String MOD_ID = "survivalfabric";
|
||||
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
public static final String MOD_ID = "survivalfabric";
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, new String[] { "spectator", "s", "S", "camera", "c", "C", }));
|
||||
}
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Scheduler.initialize();
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, "spectator", "s", "S", "camera", "c", "C"));
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, "reloadsurvivalconfig"));
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, "slimechunk", "sc"));
|
||||
|
||||
if (getConfig().sharedEnderChestEnabled)
|
||||
new SharedEnderChest().onInitialize();
|
||||
|
||||
if (getConfig().veinMinerEnabled) {
|
||||
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
|
||||
if (player instanceof ServerPlayerEntity serverPlayer && getConfig().veinMinerEnabled) {
|
||||
return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
ServerEntityEvents.ENTITY_LOAD.register(VeinMinerEvents::onEntityLoad);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package wtf.hak.survivalfabric.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||
|
||||
public class ReloadConfigCommand {
|
||||
|
||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, String... aliases) {
|
||||
for (String str : aliases)
|
||||
dispatcher.register(CommandManager.literal(str)
|
||||
.requires(source -> source.hasPermissionLevel(2))
|
||||
.executes(ReloadConfigCommand::execute));
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<ServerCommandSource> context) {
|
||||
ConfigManager.load();
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package wtf.hak.survivalfabric.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.random.ChunkRandom;
|
||||
import net.minecraft.util.math.random.Random;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
||||
|
||||
public class SlimeChunkCommand {
|
||||
|
||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, String... aliases) {
|
||||
for (String str : aliases)
|
||||
dispatcher.register(CommandManager.literal(str)
|
||||
.executes(SlimeChunkCommand::execute));
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<ServerCommandSource> context) {
|
||||
ServerCommandSource source = context.getSource();
|
||||
if (source.getEntity() == null) {
|
||||
source.sendMessage(Text.literal("Console cannot go into spectator mode!"));
|
||||
return 0;
|
||||
}
|
||||
ServerPlayerEntity p = (ServerPlayerEntity) source.getEntity();
|
||||
Chunk chunk = p.getServerWorld().getChunk(p.getBlockPos());
|
||||
Random slimeRandom = ChunkRandom.getSlimeRandom(chunk.getPos().x, chunk.getPos().z, p.getServerWorld().getSeed(), 987234911L);
|
||||
if (slimeRandom.nextInt(10) == 0) {
|
||||
p.sendMessage(Text.literal(getConfig().inSlimeChunkMessage));
|
||||
} else
|
||||
p.sendMessage(Text.literal(getConfig().notInSlimeChunkMessage));
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.world.GameMode;
|
||||
import wtf.hak.survivalfabric.utils.PacketUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@ -19,22 +20,23 @@ public class SpectatorCommand {
|
||||
|
||||
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, String... aliases) {
|
||||
for (String str : aliases)
|
||||
dispatcher.register(CommandManager.literal(str) // Ensure correct method name
|
||||
dispatcher.register(CommandManager.literal(str)
|
||||
.executes(SpectatorCommand::execute));
|
||||
}
|
||||
|
||||
private static int execute(CommandContext<ServerCommandSource> context) {
|
||||
ServerCommandSource source = context.getSource();
|
||||
if (source.getEntity() == null) { // Ensure correct method name
|
||||
if (source.getEntity() == null) {
|
||||
source.sendMessage(Text.literal("Console cannot go into spectator mode!"));
|
||||
return 0;
|
||||
}
|
||||
ServerPlayerEntity player = (ServerPlayerEntity) source.getEntity(); // Ensure correct method name
|
||||
ServerPlayerEntity player = (ServerPlayerEntity) source.getEntity();
|
||||
if (spectating.containsKey(player)) {
|
||||
LocationData data = spectating.get(player);
|
||||
player.teleport(data.world, data.x, data.y, data.z, Set.of(), data.yaw, data.pitch, false);
|
||||
player.changeGameMode(GameMode.SURVIVAL);
|
||||
spectating.remove(player);
|
||||
PacketUtils.updateListNames(player);
|
||||
} else {
|
||||
|
||||
spectating.put(player, new LocationData(player
|
||||
@ -45,6 +47,7 @@ public class SpectatorCommand {
|
||||
.getPitch(), player
|
||||
.getServerWorld()));
|
||||
player.changeGameMode(GameMode.SPECTATOR);
|
||||
PacketUtils.updateListNames(player);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
57
src/main/java/wtf/hak/survivalfabric/config/Config.java
Normal file
57
src/main/java/wtf/hak/survivalfabric/config/Config.java
Normal file
@ -0,0 +1,57 @@
|
||||
package wtf.hak.survivalfabric.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.screen.GenericContainerScreenHandler;
|
||||
import net.minecraft.screen.ScreenHandlerType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Config {
|
||||
|
||||
public String configVersion = "1.1";
|
||||
|
||||
public boolean joinMessageEnabled = true;
|
||||
public String joinMessage = "§8[§a+§8] §7%s";
|
||||
|
||||
public boolean quitMessageEnabled = true;
|
||||
public String quitMessage = "§8[§c-§8] §7%s";
|
||||
|
||||
public boolean chatMessageEnabled = true;
|
||||
public String chatMessage = "§7%s§8:§f %s";
|
||||
|
||||
public boolean dimensionIndicatorEnabled = true;
|
||||
public String overworldPrefix = "§8[§aOverworld§8] ";
|
||||
public String netherPrefix = "§8[§cNether§8] ";
|
||||
public String endPrefix = "§8[§dEnd§8] ";
|
||||
public String spectatorPrefix = "§8[§eSpectator§8] ";
|
||||
public String unknownPrefix = "§8[§7Unknown§8] ";
|
||||
|
||||
public boolean sharedEnderChestEnabled = true;
|
||||
public String sharedEnderChestName = "Ender Chest";
|
||||
public int sharedEnderChestRows = 6;
|
||||
public boolean sharedEnderChestLimitedAccess = false;
|
||||
public List<String> sharedEnderChestNames = Lists.newArrayList("AlwaysHAK");
|
||||
|
||||
public String inSlimeChunkMessage = "§aYou're currently in a slime chunk";
|
||||
public String notInSlimeChunkMessage = "§cYou're currently not in a slime chunk";
|
||||
|
||||
public boolean veinMinerEnabled = true;
|
||||
public int maxVeinSize = 99999;
|
||||
public long veinAnimationTicks = 0;
|
||||
|
||||
public boolean chatCalcEnabled = true;
|
||||
|
||||
public boolean replenishEnabled = false;
|
||||
|
||||
public ScreenHandlerType<GenericContainerScreenHandler> screenHandlerType() {
|
||||
return switch (sharedEnderChestRows) {
|
||||
case 1 -> ScreenHandlerType.GENERIC_9X1;
|
||||
case 2 -> ScreenHandlerType.GENERIC_9X2;
|
||||
case 3 -> ScreenHandlerType.GENERIC_9X3;
|
||||
case 4 -> ScreenHandlerType.GENERIC_9X4;
|
||||
case 5 -> ScreenHandlerType.GENERIC_9X5;
|
||||
case 6 -> ScreenHandlerType.GENERIC_9X6;
|
||||
default -> ScreenHandlerType.GENERIC_9X3;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package wtf.hak.survivalfabric.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ConfigManager {
|
||||
|
||||
private static final File CONFIG_FILE = FabricLoader.getInstance().getConfigDir().resolve("survivalfabric.json").toFile();
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private static Config INSTANCE;
|
||||
|
||||
public static Config getConfig() {
|
||||
if (INSTANCE == null) {
|
||||
return load();
|
||||
} else
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static Config load() {
|
||||
try (FileReader reader = new FileReader(CONFIG_FILE)) {
|
||||
INSTANCE = GSON.fromJson(reader, Config.class);
|
||||
if (INSTANCE.configVersion.equalsIgnoreCase(new Config().configVersion)) {
|
||||
return INSTANCE;
|
||||
}
|
||||
INSTANCE.configVersion = new Config().configVersion;
|
||||
save(INSTANCE);
|
||||
return INSTANCE;
|
||||
} catch (IOException e) {
|
||||
Config config = new Config();
|
||||
INSTANCE = config;
|
||||
save(config);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
public static void save(Config config) {
|
||||
try (FileWriter writer = new FileWriter(CONFIG_FILE)) {
|
||||
GSON.toJson(config, writer);
|
||||
} catch (IOException e) {
|
||||
System.out.println("Error saving config: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package wtf.hak.survivalfabric.features.sharedenderchest;
|
||||
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
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.entity.player.PlayerEntity;
|
||||
import net.minecraft.inventory.Inventories;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.NbtSizeTracker;
|
||||
import net.minecraft.screen.GenericContainerScreenHandler;
|
||||
import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvents;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
import net.minecraft.util.collection.DefaultedList;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
|
||||
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
|
||||
|
||||
public class SharedEnderChest implements ServerLifecycleEvents.ServerStopping, ServerLifecycleEvents.ServerStarted, ServerTickEvents.EndTick {
|
||||
|
||||
private static SharedInventory sharedInventory;
|
||||
|
||||
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) {
|
||||
File inventoryFile = getFile(server);
|
||||
if (inventoryFile.exists()) {
|
||||
try (FileInputStream inventoryFileInputStream = new FileInputStream(inventoryFile);
|
||||
DataInputStream inventoryFileDataInput = new DataInputStream(inventoryFileInputStream)) {
|
||||
NbtCompound nbt = NbtIo.readCompressed(inventoryFileDataInput, NbtSizeTracker.ofUnlimitedBytes());
|
||||
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
|
||||
Inventories.readNbt(nbt, inventoryItemStacks, server.getRegistryManager());
|
||||
sharedInventory = new SharedInventory(inventoryItemStacks);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error while loading Shared Ender Chest: " + e);
|
||||
sharedInventory = new SharedInventory(getConfig().sharedEnderChestRows);
|
||||
}
|
||||
} else {
|
||||
sharedInventory = new SharedInventory(getConfig().sharedEnderChestRows);
|
||||
}
|
||||
}
|
||||
|
||||
public void onServerStopping(MinecraftServer server) {
|
||||
saveInventory(server);
|
||||
}
|
||||
|
||||
public void onEndTick(MinecraftServer server) {
|
||||
if (ticksUntilSave != -20 && --ticksUntilSave <= 0L) {
|
||||
saveInventory(server);
|
||||
ticksUntilSave = 20L;
|
||||
}
|
||||
}
|
||||
|
||||
public void onInitialize() {
|
||||
ticksUntilSave = 20L;
|
||||
|
||||
UseBlockCallback listenerUseBlock = (player, world, hand, hitResult) -> {
|
||||
|
||||
if (world.getBlockState(hitResult.getBlockPos()).getBlock() instanceof EnderChestBlock) {
|
||||
if (!player.isSpectator()) {
|
||||
if (!getConfig().sharedEnderChestLimitedAccess) {
|
||||
if (world.isClient()) return ActionResult.SUCCESS;
|
||||
openSharedEnderChest(player, world, hitResult.getBlockPos());
|
||||
return ActionResult.SUCCESS;
|
||||
} else {
|
||||
for (String name : getConfig().sharedEnderChestNames) {
|
||||
if (name.toLowerCase().strip().equalsIgnoreCase(player.getNameForScoreboard().toLowerCase())) {
|
||||
if (world.isClient()) return ActionResult.SUCCESS;
|
||||
openSharedEnderChest(player, world, hitResult.getBlockPos());
|
||||
return ActionResult.SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ActionResult.PASS;
|
||||
};
|
||||
|
||||
UseBlockCallback.EVENT.register(listenerUseBlock);
|
||||
ServerLifecycleEvents.SERVER_STARTED.register(this);
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register(this);
|
||||
ServerTickEvents.END_SERVER_TICK.register(this);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package wtf.hak.survivalfabric.features.sharedenderchest;
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.inventory.Inventories;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.collection.DefaultedList;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SharedInventory implements Inventory {
|
||||
|
||||
public final Map<PlayerEntity, BlockPos> openedEnderChests = new HashMap<>();
|
||||
private final DefaultedList<ItemStack> stacks;
|
||||
|
||||
public SharedInventory(int inventoryRows) {
|
||||
this.stacks = DefaultedList.ofSize(inventoryRows * 9, ItemStack.EMPTY);
|
||||
}
|
||||
|
||||
public SharedInventory(DefaultedList<ItemStack> dl) {
|
||||
this.stacks = dl;
|
||||
}
|
||||
|
||||
|
||||
public DefaultedList<ItemStack> getList(DefaultedList<ItemStack> dl) {
|
||||
dl = stacks;
|
||||
return dl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return stacks.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
var var1 = this.stacks.iterator();
|
||||
|
||||
ItemStack itemStack_1;
|
||||
do {
|
||||
if (!var1.hasNext()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
itemStack_1 = var1.next();
|
||||
} while (itemStack_1.isEmpty());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getStack(int i) {
|
||||
return i >= stacks.size() ? ItemStack.EMPTY : stacks.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack removeStack(int int_1, int int_2) {
|
||||
return Inventories.splitStack(this.stacks, int_1, int_2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack removeStack(int i) {
|
||||
return Inventories.removeStack(this.stacks, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStack(int i, ItemStack itemStack) {
|
||||
this.stacks.set(i, itemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDirty() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayerUse(PlayerEntity playerEntity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package wtf.hak.survivalfabric.features.veinminer;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
public interface Drill {
|
||||
boolean canHandle(BlockState blockState);
|
||||
|
||||
boolean isRightTool(BlockPos pos);
|
||||
|
||||
boolean drill(BlockPos blockPos);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package wtf.hak.survivalfabric.features.veinminer;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import wtf.hak.survivalfabric.features.veinminer.drills.LeavesDrill;
|
||||
import wtf.hak.survivalfabric.features.veinminer.drills.OreDrill;
|
||||
import wtf.hak.survivalfabric.features.veinminer.drills.WoodDrill;
|
||||
|
||||
public class VeinMinerEvents {
|
||||
|
||||
public static boolean beforeBlockBreak(World world, ServerPlayerEntity player, BlockPos pos, BlockState state) {
|
||||
if (world.isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isVeinMining = VeinMinerSession.sessionForPlayer(player) != null;
|
||||
boolean canVeinMine = player.isInSneakingPose();
|
||||
if (canVeinMine && !isVeinMining) {
|
||||
VeinMinerSession session = VeinMinerSession.start(player, (ServerWorld) world, pos);
|
||||
boolean shouldContinue = !mine(session);
|
||||
session.finish();
|
||||
return shouldContinue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void onEntityLoad(Entity entity, ServerWorld world) {
|
||||
BlockPos pos = entity.getBlockPos();
|
||||
VeinMinerSession session = VeinMinerSession.sessionForPosition(pos);
|
||||
if (session != null) {
|
||||
entity.setPos(session.initialPos.getX(), session.initialPos.getY(), session.initialPos.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean mine(VeinMinerSession session) {
|
||||
Drill[] drills = new Drill[]{
|
||||
new OreDrill(session),
|
||||
new WoodDrill(session),
|
||||
new LeavesDrill(session)
|
||||
};
|
||||
|
||||
BlockPos pos = session.initialPos;
|
||||
BlockState blockState = session.world.getBlockState(pos);
|
||||
for (Drill drill : drills) {
|
||||
if (drill.canHandle(blockState) && drill.isRightTool(pos)) {
|
||||
return drill.drill(pos);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package wtf.hak.survivalfabric.features.veinminer;
|
||||
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class VeinMinerSession {
|
||||
private static final ArrayList<VeinMinerSession> sessions = new ArrayList<>();
|
||||
|
||||
public ServerPlayerEntity player;
|
||||
public ServerWorld world;
|
||||
public Set<BlockPos> positions;
|
||||
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) {
|
||||
for (var session : sessions) {
|
||||
if (session.player == player) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static VeinMinerSession sessionForPosition(BlockPos position) {
|
||||
for (var session : sessions) {
|
||||
if (session.positions.contains(position)) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static VeinMinerSession start(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) {
|
||||
var session = new VeinMinerSession(player, world, initialPos);
|
||||
sessions.add(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
private static void finish(VeinMinerSession session) {
|
||||
sessions.remove(session);
|
||||
}
|
||||
|
||||
public void addPosition(BlockPos pos) {
|
||||
positions.add(pos);
|
||||
}
|
||||
|
||||
public void removePosition(BlockPos pos) {
|
||||
positions.remove(pos);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
finish(this);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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")));
|
||||
}
|
||||
|
||||
}
|
@ -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")));
|
||||
}
|
||||
|
||||
}
|
64
src/main/java/wtf/hak/survivalfabric/mixin/BlockMixin.java
Normal file
64
src/main/java/wtf/hak/survivalfabric/mixin/BlockMixin.java
Normal file
@ -0,0 +1,64 @@
|
||||
package wtf.hak.survivalfabric.mixin;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.CropBlock;
|
||||
import net.minecraft.entity.EquipmentSlot;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.HoeItem;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.stat.Stats;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.GameMode;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(Block.class)
|
||||
public abstract class BlockMixin {
|
||||
|
||||
@Inject(method = "onBreak", at = @At("HEAD"), cancellable = true)
|
||||
public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player, CallbackInfoReturnable<BlockState> cir) {
|
||||
if (world.isClient()) return;
|
||||
|
||||
if (state.getBlock() instanceof CropBlock && ConfigManager.getConfig().replenishEnabled) {
|
||||
ItemStack mainHand = player.getStackInHand(Hand.MAIN_HAND);
|
||||
if (mainHand.getItem() instanceof HoeItem) {
|
||||
Item seedItem = state.getBlock().asItem();
|
||||
Block seedBlock = state.getBlock();
|
||||
List<ItemStack> drops = Block.getDroppedStacks(state, (ServerWorld) world, pos, null, player, mainHand);
|
||||
if (removeIfAvailable(drops, seedItem)) {
|
||||
if (player.getGameMode() != GameMode.CREATIVE) {
|
||||
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;
|
||||
}
|
||||
}
|
@ -6,9 +6,7 @@ import net.minecraft.network.message.SignedMessage;
|
||||
import net.minecraft.server.PlayerManager;
|
||||
import net.minecraft.server.network.ConnectedClientData;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.MutableText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TextContent;
|
||||
import net.minecraft.world.GameMode;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@ -16,40 +14,86 @@ import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import wtf.hak.survivalfabric.commands.SpectatorCommand;
|
||||
import wtf.hak.survivalfabric.utils.Messages;
|
||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||
import wtf.hak.survivalfabric.utils.MathUtils;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(PlayerManager.class)
|
||||
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")})
|
||||
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
|
||||
Text text = Text.literal(String.format(Messages.JOIN_MESSAGE, player.getName().getString()));
|
||||
player.sendMessage(text, false);
|
||||
if (ConfigManager.getConfig().joinMessageEnabled && !player.getServer().isSingleplayer()) {
|
||||
Text text = Text.literal(String.format(ConfigManager.getConfig().joinMessage, player.getName().getString()));
|
||||
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"))
|
||||
private Text onPlayerConnect(Text text) {
|
||||
String name = text.getString().split(" ")[0];
|
||||
return Text.literal(String.format(Messages.JOIN_MESSAGE, name));
|
||||
if (ConfigManager.getConfig().joinMessageEnabled) {
|
||||
String name = text.getString().split(" ")[0];
|
||||
return Text.literal(String.format(ConfigManager.getConfig().joinMessage, name));
|
||||
} else
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get player out of specator mode if necessary
|
||||
*/
|
||||
@Inject(method = {"remove"}, at = {@At("HEAD")})
|
||||
public void onPlayerLeave(ServerPlayerEntity player, CallbackInfo ci) {
|
||||
if(SpectatorCommand.spectating.containsKey(player)) {
|
||||
if (SpectatorCommand.spectating.containsKey(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
private void onBroadcast(SignedMessage message, ServerPlayerEntity sender, MessageType.Parameters parameters, CallbackInfo ci) {
|
||||
if(sender != null) {
|
||||
Text text = Text.literal(String.format(Messages.CHAT_FORMAT, sender.getName().getString(), message.getContent().getString()));
|
||||
sender.getServer().getPlayerManager().broadcast(text, false);
|
||||
if (sender != null) {
|
||||
String rawMessage = message.getContent().getString().trim();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,14 +5,21 @@ import net.minecraft.text.Text;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import wtf.hak.survivalfabric.utils.Messages;
|
||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||
|
||||
@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"))
|
||||
private Text quitMessage(Text text) {
|
||||
String name = text.getString().split(" ")[0];
|
||||
return Text.literal(String.format(Messages.QUIT_MESSAGE, name));
|
||||
if (ConfigManager.getConfig().quitMessageEnabled) {
|
||||
String name = text.getString().split(" ")[0];
|
||||
return Text.literal(String.format(ConfigManager.getConfig().quitMessage, name));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
package wtf.hak.survivalfabric.mixin;
|
||||
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
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.commands.SpectatorCommand;
|
||||
import wtf.hak.survivalfabric.config.ConfigManager;
|
||||
|
||||
@Mixin(ServerPlayerEntity.class)
|
||||
public class ServerPlayerEntityMixin {
|
||||
|
||||
/**
|
||||
* Change player list name if enabled
|
||||
*/
|
||||
@Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true)
|
||||
private void changePlayerListName(CallbackInfoReturnable<Text> cir) {
|
||||
if (ConfigManager.getConfig().dimensionIndicatorEnabled) {
|
||||
ServerPlayerEntity p = (ServerPlayerEntity) (Object) this;
|
||||
String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey();
|
||||
String finalName;
|
||||
if (!SpectatorCommand.spectating.containsKey(p)) {
|
||||
finalName = switch (world) {
|
||||
case "minecraft.overworld" -> ConfigManager.getConfig().overworldPrefix;
|
||||
case "minecraft.the_nether" -> ConfigManager.getConfig().netherPrefix;
|
||||
case "minecraft.the_end" -> ConfigManager.getConfig().endPrefix;
|
||||
default -> ConfigManager.getConfig().unknownPrefix;
|
||||
};
|
||||
} else
|
||||
finalName = ConfigManager.getConfig().spectatorPrefix;
|
||||
|
||||
finalName += "§7" + p.getName().getString();
|
||||
|
||||
cir.setReturnValue(Text.of(finalName));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package wtf.hak.survivalfabric.mixin;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
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.utils.PacketUtils;
|
||||
|
||||
@Mixin(ServerWorld.class)
|
||||
public class ServerWorldMixin {
|
||||
|
||||
/**
|
||||
* Update List Names if needed
|
||||
*/
|
||||
@Inject(method = "onDimensionChanged", at = {@At("HEAD")})
|
||||
public void onDimensionChange(Entity entity, CallbackInfo ci) {
|
||||
if (entity instanceof ServerPlayerEntity) {
|
||||
PacketUtils.updateListNames((ServerPlayerEntity) entity);
|
||||
}
|
||||
}
|
||||
}
|
64
src/main/java/wtf/hak/survivalfabric/utils/MathUtils.java
Normal file
64
src/main/java/wtf/hak/survivalfabric/utils/MathUtils.java
Normal file
@ -0,0 +1,64 @@
|
||||
package wtf.hak.survivalfabric.utils;
|
||||
|
||||
public class MathUtils {
|
||||
|
||||
|
||||
public static double clamp(double value, double min, double max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
public static float clamp(float value, float min, float max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
public static int clamp(int value, int min, int max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
public static boolean hasSupportedOperator(String msg) {
|
||||
return msg.contains("+") || msg.contains("-") || msg.contains("*") || msg.contains("/");
|
||||
}
|
||||
|
||||
public static double evaluateExpression(String expression) {
|
||||
return evaluate(expression.replaceAll("\\s", ""), new int[]{0});
|
||||
}
|
||||
|
||||
public static double evaluate(String expr, int[] index) {
|
||||
double value = parseTerm(expr, index);
|
||||
while (index[0] < expr.length()) {
|
||||
char op = expr.charAt(index[0]);
|
||||
if (op != '+' && op != '-') break;
|
||||
index[0]++;
|
||||
double nextTerm = parseTerm(expr, index);
|
||||
value = (op == '+') ? value + nextTerm : value - nextTerm;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static double parseTerm(String expr, int[] index) {
|
||||
double value = parseFactor(expr, index);
|
||||
while (index[0] < expr.length()) {
|
||||
char op = expr.charAt(index[0]);
|
||||
if (op != '*' && op != '/') break;
|
||||
index[0]++;
|
||||
double nextFactor = parseFactor(expr, index);
|
||||
value = (op == '*') ? value * nextFactor : value / nextFactor;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static double parseFactor(String expr, int[] index) {
|
||||
if (expr.charAt(index[0]) == '(') {
|
||||
index[0]++;
|
||||
double value = evaluate(expr, index);
|
||||
index[0]++; // Skip closing ')'
|
||||
return value;
|
||||
}
|
||||
|
||||
int start = index[0];
|
||||
while (index[0] < expr.length() && (Character.isDigit(expr.charAt(index[0])) || expr.charAt(index[0]) == '.')) {
|
||||
index[0]++;
|
||||
}
|
||||
return Double.parseDouble(expr.substring(start, index[0]));
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package wtf.hak.survivalfabric.utils;
|
||||
|
||||
public class Messages {
|
||||
public static final String JOIN_MESSAGE = "§8[§a+§8] §7%s";
|
||||
|
||||
public static final String QUIT_MESSAGE = "§8[§c-§8] §7%s";
|
||||
|
||||
public static final String CHAT_FORMAT = "§7%s§8:§f %s";
|
||||
}
|
15
src/main/java/wtf/hak/survivalfabric/utils/PacketUtils.java
Normal file
15
src/main/java/wtf/hak/survivalfabric/utils/PacketUtils.java
Normal file
@ -0,0 +1,15 @@
|
||||
package wtf.hak.survivalfabric.utils;
|
||||
|
||||
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PacketUtils {
|
||||
|
||||
public static void updateListNames(ServerPlayerEntity p) {
|
||||
for (ServerPlayerEntity sp : Objects.requireNonNull(p.getServer()).getPlayerManager().getPlayerList()) {
|
||||
sp.networkHandler.sendPacket(new PlayerListS2CPacket(PlayerListS2CPacket.Action.UPDATE_DISPLAY_NAME, p));
|
||||
}
|
||||
}
|
||||
}
|
45
src/main/java/wtf/hak/survivalfabric/utils/Scheduler.java
Normal file
45
src/main/java/wtf/hak/survivalfabric/utils/Scheduler.java
Normal file
@ -0,0 +1,45 @@
|
||||
package wtf.hak.survivalfabric.utils;
|
||||
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Scheduler {
|
||||
|
||||
private static Scheduler INSTANCE;
|
||||
|
||||
private final Map<Runnable, Long> tasks = new ConcurrentHashMap<>();
|
||||
|
||||
public Scheduler() {
|
||||
ServerTickEvents.END_SERVER_TICK.register((server) -> {
|
||||
for(Runnable task : tasks.keySet()) {
|
||||
long delay = tasks.get(task);
|
||||
if(delay <= 0) {
|
||||
task.run();
|
||||
tasks.remove(task);
|
||||
} else {
|
||||
tasks.put(task, delay-1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void scheduleTask(Runnable task) {
|
||||
scheduleTask(task, 0L);
|
||||
}
|
||||
|
||||
public void scheduleTask(Runnable task, long delay) {
|
||||
tasks.put(task, delay);
|
||||
}
|
||||
|
||||
public static Scheduler get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static Scheduler initialize() {
|
||||
INSTANCE = new Scheduler();
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.2 KiB |
22
src/main/resources/assets/survivalfabric/lang/en_us.json
Normal file
22
src/main/resources/assets/survivalfabric/lang/en_us.json
Normal 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"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"#minecraft:leaves"
|
||||
]
|
||||
}
|
14
src/main/resources/data/survivalfabric/tags/block/ore.json
Normal file
14
src/main/resources/data/survivalfabric/tags/block/ore.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"values": [
|
||||
"#minecraft:iron_ores",
|
||||
"#minecraft:gold_ores",
|
||||
"#minecraft:lapis_ores",
|
||||
"#minecraft:redstone_ores",
|
||||
"#minecraft:diamond_ores",
|
||||
"#minecraft:emerald_ores",
|
||||
"#minecraft:coal_ores",
|
||||
"#minecraft:copper_ores",
|
||||
"minecraft:ancient_debris",
|
||||
"minecraft:nether_quartz_ore"
|
||||
]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"values": [
|
||||
"#minecraft:logs"
|
||||
]
|
||||
}
|
@ -1,37 +1,48 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "survivalfabric",
|
||||
"version": "${version}",
|
||||
"name": "SurvivalFabric",
|
||||
"description": "Adds a few QOL features to your Survival!",
|
||||
"authors": [
|
||||
"AlwaysHAK"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://hak.wtf",
|
||||
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric"
|
||||
},
|
||||
"license": "CC0-1.0",
|
||||
"icon": "assets/survivalfabric/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"wtf.hak.survivalfabric.SurvivalFabric"
|
||||
],
|
||||
"fabric-datagen": [
|
||||
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"survivalfabric.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.16.10",
|
||||
"minecraft": "~1.21.4",
|
||||
"java": ">=21",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"suggests": {
|
||||
|
||||
}
|
||||
"schemaVersion": 1,
|
||||
"id": "survivalfabric",
|
||||
"version": "${version}",
|
||||
"name": "SurvivalFabric",
|
||||
"description": "Adds a few QOL features to your Survival!",
|
||||
"authors": [
|
||||
"AlwaysHAK"
|
||||
],
|
||||
"contact": {
|
||||
"homepage": "https://hak.wtf",
|
||||
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric",
|
||||
"issues": "https://git.hak.wtf/hkuijlman/SurvivalFabric/issues"
|
||||
},
|
||||
"license": "CC0-1.0",
|
||||
"icon": "assets/survivalfabric/icon.png",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"main": [
|
||||
"wtf.hak.survivalfabric.SurvivalFabric"
|
||||
],
|
||||
"client": [
|
||||
"wtf.hak.survivalfabric.SurvivalFabricClient"
|
||||
],
|
||||
"fabric-datagen": [
|
||||
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
|
||||
],
|
||||
"modmenu": [
|
||||
"wtf.hak.survivalfabric.modmenu.ModMenuIntegration"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
"survivalfabric.mixins.json",
|
||||
{
|
||||
"config": "survivalfabric.client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.16.10",
|
||||
"minecraft": "~1.21.5",
|
||||
"java": ">=21",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"optional": {
|
||||
"modmenu": "*"
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "wtf.hak.survivalfabric.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"PlayerManagerMixin",
|
||||
"ServerPlayNetworkHandlerMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
"required": true,
|
||||
"package": "wtf.hak.survivalfabric.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"BlockMixin",
|
||||
"PlayerManagerMixin",
|
||||
"ServerPlayerEntityMixin",
|
||||
"ServerPlayNetworkHandlerMixin",
|
||||
"ServerWorldMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user