26 Commits

Author SHA1 Message Date
92e9c996b9 Updated README for 1.4.1 release
All checks were successful
build / build (push) Successful in 1m17s
2025-05-20 12:20:49 +02:00
dd50ff0376 Fixed BlockMixin for 1.21.4
All checks were successful
build / build (push) Successful in 1m16s
2025-05-20 12:15:16 +02:00
d2fd497f26 Merge branch 'master' into 1.21.4 2025-05-20 12:10:17 +02:00
a6f8645c68 Added toggleable scrollToZoom
All checks were successful
build / build (push) Successful in 1m13s
2025-05-20 12:01:59 +02:00
2be7c16e8b Refactored SFKeyBindings to CameraShortcut 2025-05-20 11:59:51 +02:00
ccbb15f7c4 Fixed bug in Replenish feature
All checks were successful
build / build (push) Successful in 1m43s
2025-05-19 22:23:08 +02:00
05a93b5ab0 Updated README.md
All checks were successful
build / build (push) Successful in 1m14s
2025-05-19 20:36:47 +02:00
327c92a81c Updated README.md 2025-05-19 20:32:24 +02:00
29067365ca Added Zoom functionality 2025-05-19 20:32:00 +02:00
1ae193ada7 Greatly improved Vein Miner performance 2025-05-19 20:31:13 +02:00
151fd022c4 Cleaned up code
All checks were successful
build / build (push) Successful in 1m25s
2025-05-19 12:06:39 +02:00
deb338f2c8 Added configurable Vein Miner animation 2025-05-19 11:59:39 +02:00
61d2a0b137 Updated README.md 2025-05-19 10:20:58 +02:00
35148c2159 Added Replenish function (replanting crops) 2025-05-19 10:20:47 +02:00
ef29652e07 Made leaves veinmineable using shears 2025-05-19 10:15:29 +02:00
563febe36f Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m16s
# Conflicts:
#	src/main/resources/fabric.mod.json
2025-04-12 17:31:34 +02:00
38a244b023 Added correct ModMenu version for 1.21.4
All checks were successful
build / build (push) Successful in 1m21s
2025-04-07 10:37:17 +02:00
868e71eb10 Fixed typo 2025-04-07 10:37:07 +02:00
2a106c9a03 Merge branch 'master' into 1.21.4
# Conflicts:
#	gradle.properties
2025-04-07 10:33:20 +02:00
1d0f7f0f05 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m9s
2025-04-06 12:25:41 +02:00
52a97dc2c1 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m11s
2025-03-30 23:33:15 +02:00
5916cc5cc2 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m2s
2025-03-30 18:16:16 +02:00
4e58bede4b Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m5s
2025-03-30 17:58:58 +02:00
130b2db727 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:28:42 +02:00
c993621bdb Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m7s
2025-03-30 17:13:15 +02:00
b89ab99928 Downgraded mod to 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:06:42 +02:00
17 changed files with 332 additions and 288 deletions

View File

@ -8,7 +8,6 @@ As a challenge I'm trying to make it as user-friendly as possible.
## Server Side
### Features
- Custom join message
![Join Message](https://i.imgur.com/7uv5lUb.png)
- Custom quit message
@ -29,8 +28,14 @@ As a challenge I'm trying to make it as user-friendly as possible.
- 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!
![VeinMiner](https://i.imgur.com/zOXWMNa.gif)
- 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.
@ -43,30 +48,26 @@ As a challenge I'm trying to make it as user-friendly as possible.
![Teleportation Keybindings](https://i.imgur.com/gjO1H3d.png)
- Remove game fog (lava, water, etc.)
- All types individually toggleable
- Mod Menu integration
- Automatic config adaption (currently booleans only)
- Remove darkness effect
- Toggleable
- Keybinding for /spectator
# Currently working on
## Client Side
- [x] Render block entities from a longer range
- [x] Toggleable via GUI
- [x] Configurable value
- [x] In GUI
- [x] Rework Mod Menu integration to be more flexible
- 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
- [x] Chat Calculator
- [x] Check if operator is present before calculating
- [x] Zoom
- [ ] Configurable
- Zoom
- Configurable
- Smooth zoom
- Initial zoom value
- Zoom step value
- Scroll to zoom further
# To-do

View File

@ -4,8 +4,8 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8
loader_version=0.16.10
# Mod Properties
@ -14,6 +14,6 @@ maven_group=wtf.hak.survivalfabric
archives_base_name=survivalfabric
# Dependencies
fabric_version=0.119.5+1.21.5
fabric_version=0.119.0+1.21.4
modmenu_version=14.0.0-rc.2
modmenu_version=13.0.3

View File

@ -4,7 +4,7 @@ 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.SFKeyBindings;
import wtf.hak.survivalfabric.features.CameraShortcut;
import wtf.hak.survivalfabric.features.Zoom;
public class SurvivalFabricClient implements ClientModInitializer {
@ -18,7 +18,7 @@ public class SurvivalFabricClient implements ClientModInitializer {
// Features
AngleViewer.register();
RemoveDarknessEffect.register();
SFKeyBindings.register();
CameraShortcut.register();
Zoom.register();
}
}

View File

@ -1,7 +1,7 @@
package wtf.hak.survivalfabric.config.client;
public class ClientConfig {
public String configVersion = "1.0";
public String configVersion = "1.1";
public boolean renderNetherFog = false;
public boolean renderOverworldFog = false;
@ -13,4 +13,8 @@ public class ClientConfig {
public boolean lockTeleportHeadMovement = true;
public boolean manipulateBlockEntityDistance = true;
public int blockEntityRange = 512;
public boolean smoothCamera = true;
public float initialZoom = 20f;
public boolean scrollToZoom = true;
public float zoomStep = 2.5f;
}

View File

@ -6,7 +6,7 @@ import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
public class SFKeyBindings {
public class CameraShortcut {
private static final KeyBinding CAMERA_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.camera", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_PERIOD, "category.survivalfabric.survivalfabric"));

View File

@ -7,24 +7,37 @@ 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 final float INITIAL_ZOOM = 20f;
private static boolean SHOULD_ZOOM = false;
private static float ZOOM_STEP_VALUE = 2.5f;
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())
if (ZOOM_BIND.isPressed() && !SHOULD_ZOOM) {
if(getConfig().smoothCamera) {
initialSmoothZoom = MinecraftClient.getInstance().options.smoothCameraEnabled;
MinecraftClient.getInstance().options.smoothCameraEnabled = true;
}
SHOULD_ZOOM = true;
else {
} else if (!ZOOM_BIND.isPressed() && SHOULD_ZOOM) {
SHOULD_ZOOM = false;
ZOOM_STEP = 0;
if(getConfig().smoothCamera) {
MinecraftClient.getInstance().options.smoothCameraEnabled = initialSmoothZoom;
}
}
});
}
@ -33,7 +46,7 @@ public class Zoom {
}
public static float getZoomFov() {
return INITIAL_ZOOM - -ZOOM_STEP * ZOOM_STEP_VALUE;
return getConfig().initialZoom - -ZOOM_STEP * getConfig().zoomStep;
}
public static void modifyStep(int step) {
@ -42,9 +55,9 @@ public class Zoom {
// Clamp the zoom level so the FOV stays within [1, 110]
float zoomFov = getZoomFov();
if (zoomFov < 1) {
ZOOM_STEP = Math.round((1 - INITIAL_ZOOM) / ZOOM_STEP_VALUE) + 1;
ZOOM_STEP = Math.round((1 - getConfig().initialZoom) / getConfig().zoomStep) + 1;
} else if (zoomFov > 110) {
ZOOM_STEP = Math.round((110 - INITIAL_ZOOM) / ZOOM_STEP_VALUE);
ZOOM_STEP = Math.round((110 - getConfig().initialZoom) / getConfig().zoomStep);
}
}
}

View File

@ -8,6 +8,8 @@ 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 {
@ -16,7 +18,7 @@ public class MouseMixin {
*/
@Inject(method = "onMouseScroll", at = @At("HEAD"), cancellable = true)
private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) {
if (Zoom.isZooming()) {
if (Zoom.isZooming() && getConfig().scrollToZoom) {
if (MinecraftClient.getInstance().player != null) {
if (vertical > 0)
Zoom.modifyStep(-1);

View File

@ -12,6 +12,7 @@ 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;
@ -23,6 +24,7 @@ public class SurvivalFabric implements ModInitializer {
@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"));

View File

@ -8,7 +8,7 @@ import java.util.List;
public class Config {
public String configVersion = "1.0";
public String configVersion = "1.1";
public boolean joinMessageEnabled = true;
public String joinMessage = "§8[§a+§8] §7%s";
@ -37,9 +37,12 @@ public class Config {
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;

View File

@ -1,6 +1,8 @@
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;
@ -8,27 +10,77 @@ 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) {
public DrillBase(VeinMinerSession session, TagKey<Block> tag) {
this.session = session;
this.tag = tag;
}
@Override
public boolean canHandle(BlockState blockId) {
return false;
public boolean canHandle(BlockState blockState) {
return blockState.isIn(tag);
}
@Override
public boolean drill(BlockPos blockPos) {
return false;
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
@ -38,9 +90,12 @@ public class DrillBase implements Drill {
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler) {
forXYZ(pos, max, handlerPos -> {
handler.handle(handlerPos);
return 0;
forXYZ(pos, max, new ForXYZCounter() {
@Override
public int handle(BlockPos pos) {
handler.handle(pos);
return 1; // Accumulate one count for each position processed
}
}, false);
}
@ -49,14 +104,17 @@ public class DrillBase implements Drill {
}
protected void forXYZ(BlockPos pos, int max, ForXYZHandler handler, boolean forceVertical) {
forXYZ(pos, max, handlerPos -> {
handler.handle(handlerPos);
return 0;
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) {
ArrayList<Integer> offsets = new ArrayList<Integer>();
Set<Integer> offsets = new HashSet<>();
for (int d = 0; d <= max; ++d) {
offsets.add(d);
if (d != -d) {
@ -64,39 +122,21 @@ public class DrillBase implements Drill {
}
}
String[] order = new String[]{"x", "y", "z"};
if (forceVertical) {
order = new String[]{"y", "x", "z"};
} else {
ServerPlayerEntity player = session.player;
boolean majorPitchChange = player.getPitch() < -45.0 || player.getPitch() > 45.0;
boolean majorYawChange = (player.getYaw() > 45.0 && player.getYaw() < 135.0) || (player.getYaw() < -45.0 && player.getYaw() > -135.0);
if (majorPitchChange) {
if (majorYawChange) {
order = new String[]{"y", "z", "x"};
} else {
order = new String[]{"y", "x", "z"};
}
} else {
if (majorYawChange) {
order = new String[]{"z", "y", "x"};
}
}
}
String[] order = determineOrder(forceVertical);
int counter = 0;
for (int i1 : offsets) {
for (int i2 : offsets) {
for (int i3 : offsets) {
int ix = order[0] == "x" ? i1 : order[1] == "x" ? i2 : i3;
int iy = order[0] == "y" ? i1 : order[1] == "y" ? i2 : i3;
int iz = order[0] == "z" ? i1 : order[1] == "z" ? i2 : i3;
int 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));
counter += handler.handle(new BlockPos(px, py, pz)); // Works with ForXYZCounter, returning an int
}
}
}
@ -104,6 +144,29 @@ public class DrillBase implements Drill {
return counter;
}
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);

View File

@ -1,62 +1,22 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.Items;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class LeavesDrill extends DrillBase {
public static final TagKey<Block> leavesTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves"));
public LeavesDrill(VeinMinerSession session) {
super(session);
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "leaves")));
}
@Override
public boolean canHandle(BlockState blockState) {
return blockState.isIn(leavesTag);
}
@Override
public boolean drill(BlockPos startPos) {
ServerWorld world = session.world;
Block initialBlock = world.getBlockState(startPos).getBlock();
int brokenLeaves = 0;
ArrayDeque<BlockPos> pending = new ArrayDeque<BlockPos>();
pending.add(startPos);
while (!pending.isEmpty() && brokenLeaves < getConfig().maxVeinSize) {
BlockPos leavesPos = pending.remove();
Block leavesBlock = world.getBlockState(leavesPos).getBlock();
if (tryBreakBlock(leavesPos)) {
if (leavesBlock == initialBlock) {
brokenLeaves += 1;
}
if (leavesBlock == initialBlock) {
// look around current block
forXYZ(leavesPos, 1, newPos -> {
BlockState newBlockState = world.getBlockState(newPos);
Block newBlock = newBlockState.getBlock();
boolean isSameOreBlock = newBlock == leavesBlock;
if (!pending.contains(newPos) && isSameOreBlock) {
pending.add(newPos);
}
});
}
}
}
return true;
public boolean isRightTool(BlockPos pos) {
var blockState = session.world.getBlockState(pos);
return session.player.getMainHandStack().isSuitableFor(blockState) || session.player.getMainHandStack().getItem() == Items.SHEARS;
}
}

View File

@ -1,62 +1,14 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class OreDrill extends DrillBase {
public static final TagKey<Block> oreTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore"));
public OreDrill(VeinMinerSession session) {
super(session);
}
@Override
public boolean canHandle(BlockState blockState) {
return blockState.isIn(oreTag);
}
@Override
public boolean drill(BlockPos startPos) {
ServerWorld world = session.world;
Block initialBlock = world.getBlockState(startPos).getBlock();
int brokenOre = 0;
ArrayDeque<BlockPos> pending = new ArrayDeque<BlockPos>();
pending.add(startPos);
while (!pending.isEmpty() && brokenOre < getConfig().maxVeinSize) {
BlockPos orePos = pending.remove();
Block oreBlock = world.getBlockState(orePos).getBlock();
if (tryBreakBlock(orePos)) {
if (oreBlock == initialBlock) {
brokenOre += 1;
}
if (oreBlock == initialBlock) {
// look around current block
forXYZ(orePos, 1, newPos -> {
BlockState newBlockState = world.getBlockState(newPos);
Block newBlock = newBlockState.getBlock();
boolean isSameOreBlock = newBlock == oreBlock;
if (!pending.contains(newPos) && isSameOreBlock) {
pending.add(newPos);
}
});
}
}
}
return true;
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore")));
}
}

View File

@ -1,80 +1,14 @@
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class WoodDrill extends DrillBase {
public static final TagKey<Block> woodTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood"));
public WoodDrill(VeinMinerSession session) {
super(session);
}
@Override
public boolean canHandle(BlockState blockState) {
return blockState.isIn(woodTag);
}
@Override
public boolean drill(BlockPos startPos) {
ServerWorld world = session.world;
int broken = 0;
ArrayDeque<BlockPos> pendingLogs = new ArrayDeque<>();
ArrayDeque<BlockPos> logBlocks = new ArrayDeque<>();
pendingLogs.add(startPos);
String leavesBlockId = Registries.BLOCK.getId(world.getBlockState(startPos).getBlock()).toString().replace("_log", "_leaves");
while (!pendingLogs.isEmpty() && broken < getConfig().maxVeinSize) {
BlockPos woodPos = pendingLogs.remove();
Block woodBlock = world.getBlockState(woodPos).getBlock();
if (tryBreakBlock(woodPos)) {
logBlocks.add(woodPos);
broken += 1;
// look around current block
forXYZ(woodPos, 1, newPos -> {
Block newBlock = world.getBlockState(newPos).getBlock();
if (newBlock == woodBlock && !pendingLogs.contains(newPos)) {
pendingLogs.add(newPos);
}
}, true);
}
}
ArrayDeque<BlockPos> pendingLeaves = logBlocks;
while (!pendingLeaves.isEmpty() && broken < getConfig().maxVeinSize) {
broken += forXYZ(pendingLeaves.remove(), 1, newPos -> {
int brokenLeaves = 0;
Block newBlock = world.getBlockState(newPos).getBlock();
String newBlockId = Registries.BLOCK.getId(newBlock).toString();
if (newBlockId.equals(leavesBlockId)) {
if (tryBreakBlock(newPos)) {
brokenLeaves += 1;
}
}
return brokenLeaves;
}, true);
}
return true;
}
private boolean isLeaf(Block block) {
return Registries.BLOCK.getId(block).toString().endsWith("_leaves");
super(session, TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood")));
}
}

View File

@ -0,0 +1,64 @@
package wtf.hak.survivalfabric.mixin;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.CropBlock;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.HoeItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.stat.Stats;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameMode;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import wtf.hak.survivalfabric.config.ConfigManager;
import java.util.List;
@Mixin(Block.class)
public abstract class BlockMixin {
@Inject(method = "onBreak", at = @At("HEAD"), cancellable = true)
public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player, CallbackInfoReturnable<BlockState> cir) {
if (world.isClient()) return;
if (state.getBlock() instanceof CropBlock && ConfigManager.getConfig().replenishEnabled) {
ItemStack mainHand = player.getStackInHand(Hand.MAIN_HAND);
if (mainHand.getItem() instanceof HoeItem) {
Item seedItem = state.getBlock().asItem();
Block seedBlock = state.getBlock();
List<ItemStack> drops = Block.getDroppedStacks(state, (ServerWorld) world, pos, null, player, mainHand);
if (removeIfAvailable(drops, seedItem)) {
if (!player.isCreative()) {
for (ItemStack drop : drops) {
Block.dropStack(world, pos, drop);
}
player.incrementStat(Stats.USED.getOrCreateStat(seedItem));
mainHand.damage(1, player, EquipmentSlot.MAINHAND);
}
world.getServer().executeSync(() -> world.setBlockState(pos, seedBlock.getDefaultState()));
cir.setReturnValue(Blocks.AIR.getDefaultState());
cir.cancel();
}
}
}
}
private boolean removeIfAvailable(List<ItemStack> drops, Item item) {
for (ItemStack drop : drops) {
if (drop.getItem() == item) {
drop.decrement(1);
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,45 @@
package wtf.hak.survivalfabric.utils;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Scheduler {
private static Scheduler INSTANCE;
private final Map<Runnable, Long> tasks = new ConcurrentHashMap<>();
public Scheduler() {
ServerTickEvents.END_SERVER_TICK.register((server) -> {
for(Runnable task : tasks.keySet()) {
long delay = tasks.get(task);
if(delay <= 0) {
task.run();
tasks.remove(task);
} else {
tasks.put(task, delay-1);
}
}
});
}
public void scheduleTask(Runnable task) {
scheduleTask(task, 0L);
}
public void scheduleTask(Runnable task, long delay) {
tasks.put(task, delay);
}
public static Scheduler get() {
return INSTANCE;
}
public static Scheduler initialize() {
INSTANCE = new Scheduler();
return INSTANCE;
}
}

View File

@ -38,7 +38,7 @@
],
"depends": {
"fabricloader": ">=0.16.10",
"minecraft": "~1.21.5",
"minecraft": "~1.21.4",
"java": ">=21",
"fabric-api": "*"
},

View File

@ -3,6 +3,7 @@
"package": "wtf.hak.survivalfabric.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"BlockMixin",
"PlayerManagerMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerMixin",