31 Commits

Author SHA1 Message Date
130b2db727 Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:28:42 +02:00
16027c04d1 Made head movement locked while "angle viewing"
All checks were successful
build / build (push) Successful in 1m6s
2025-03-30 17:26:55 +02:00
c993621bdb Merge branch 'master' into 1.21.4
All checks were successful
build / build (push) Successful in 1m7s
2025-03-30 17:13:15 +02:00
8ae9a4f7c4 Fixed annoyance 2025-03-30 17:12:27 +02:00
b89ab99928 Downgraded mod to 1.21.4
All checks were successful
build / build (push) Successful in 1m8s
2025-03-30 17:06:42 +02:00
cae8c34759 Added Teleportation Angle Viewer
All checks were successful
build / build (push) Successful in 1m8s
(Also removed FPS counter)
2025-03-30 16:16:51 +02:00
77ae4cc1f0 Removed Magma Blocks and added Quartz Ore to veinmine 2025-03-30 14:45:44 +02:00
e465963daa Created 'build.yml' workflow
All checks were successful
build / build (push) Successful in 4m0s
2025-03-27 13:19:22 +01:00
6b862dfbc3 Deleted duplicate class 2025-03-27 11:31:52 +01:00
045623a67b Split client & common sources + POC FPS counter 2025-03-27 11:03:35 +01:00
b6bb6565a8 Updated mod to 1.21.5 2025-03-26 16:50:38 +01:00
8cebb278b8 Updated mod to 1.21.5 2025-03-26 16:45:19 +01:00
f17519a547 Updated README for v1.3.0 2025-03-26 16:24:52 +01:00
8be2f2dc31 Prettified README 2025-03-26 16:00:09 +01:00
091efb900a Scrapped Telekinesis (for now) due to complexity 2025-03-26 15:10:27 +01:00
824c84e329 Fixed spelling mistake 2025-03-26 10:59:42 +01:00
73b8be0930 Added Vein Mining 2025-03-26 10:56:16 +01:00
645529a9f6 Added /slimechunk command 2025-03-26 10:04:28 +01:00
8856bec073 Updated README 2025-03-26 00:47:50 +01:00
7ad7ce6ed4 Added the Shared Ender Chest 2025-03-26 00:43:43 +01:00
78a56b6f28 Added config version "control" 2025-03-25 23:15:10 +01:00
6e15c54b80 Fixed INSTANCE not being assigned 2025-03-25 23:02:19 +01:00
22de60988f Removed unnecessary imports 2025-03-25 23:00:50 +01:00
c8f907dc57 Added customizable config messages & feature toggles 2025-03-25 22:59:05 +01:00
8377f36114 Updated README 2025-03-25 21:47:23 +01:00
f3a1156fd4 Changed mod icon 2025-03-25 21:36:53 +01:00
5eae9d4a8c Updated version number to 1.2.0 2025-03-25 21:18:47 +01:00
83cc3a38e9 Added back the tablist dimension indicator 2025-03-25 21:17:12 +01:00
7d4dca66f0 Created README.md 2025-03-25 19:35:11 +01:00
f6f0fc65d7 Removed GitHub workflow. It does not work with GitTea. 2025-03-25 17:35:29 +01:00
7b247ce2cc (Temporarily) Removed dimension indication to tab list due to a lot of bugs
Some checks failed
build / build (push) Has been cancelled
2025-03-18 21:28:04 +01:00
40 changed files with 1249 additions and 216 deletions

View File

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

View File

@ -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/

48
README.md Normal file
View File

@ -0,0 +1,48 @@
# ![Logo](https://i.imgur.com/ktkHND1.png)
This mod is FAR FROM FINISHED, and initially just created for our private Survival Server.
As a challenge I'm trying to make it as user-friendly as possible. (It ain't there yet tho ;) )
# Current feature-set
## Server Side
### Features
- Custom join message
![Join Message](https://i.imgur.com/7uv5lUb.png)
- Custom quit message
![Quit Message](https://i.imgur.com/OhFq1BT.png)
- Custom chat message
![Chat Message](https://i.imgur.com/PDwRywP.png)
- Tab list dimension indicator
![Overworld](https://i.imgur.com/FB1Y7gD.png)
![Nether](https://i.imgur.com/XxJDL7u.png)
![End](https://i.imgur.com/t5u9goh.png)
![Spectator](https://i.imgur.com/eEn4V9S.png)
- Config
- Configurable messages
- Feature toggle
- Version "control"
- Shared Ender Chest
- Shared EC Access control (via config)
- Vein miner
![VeinMiner](https://i.imgur.com/zOXWMNa.gif)
### 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
![Teleportation Keybindings](https://i.imgur.com/gjO1H3d.png)
# Features to come
## 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

View File

@ -18,6 +18,18 @@ repositories {
// for more information about repositories.
}
loom {
splitEnvironmentSourceSets()
mods {
"survivalfabric" {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
}
fabricApi {
configureDataGeneration {
client = true
@ -26,7 +38,7 @@ 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}"

View File

@ -9,7 +9,7 @@ yarn_mappings=1.21.4+build.8
loader_version=0.16.10
# Mod Properties
mod_version=1.1.0
mod_version=1.3.2
maven_group=wtf.hak.survivalfabric
archives_base_name=survivalfabric

0
gradlew vendored Normal file → Executable file
View File

View File

@ -0,0 +1,12 @@
package wtf.hak.survivalfabric;
import net.fabricmc.api.ClientModInitializer;
import wtf.hak.survivalfabric.teleportation.AngleViewHandler;
public class SurvivalFabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
AngleViewHandler.registerKeybindings();
}
}

View File

@ -0,0 +1,31 @@
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.teleportation.AngleViewHandler;
@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() && AngleViewHandler.PREVENT_HEAD_MOVEMENT) {
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() && AngleViewHandler.PREVENT_HEAD_MOVEMENT) {
ci.cancel();
}
}
}
}

View File

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

View File

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

View File

@ -3,11 +3,18 @@ package wtf.hak.survivalfabric;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.scoreboard.Team;
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.sharedenderchest.SharedEnderChest;
import wtf.hak.survivalfabric.veinminer.VeinMinerEvents;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
public class SurvivalFabric implements ModInitializer {
@ -18,6 +25,23 @@ public class SurvivalFabric implements ModInitializer {
@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, new String[] { "spectator", "s", "S", "camera", "c", "C", }));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, new String[] { "reloadsurvivalconfig" }));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, new String[] { "slimechunk", "sc" }));
if(getConfig().sharedEnderChestEnabled)
new SharedEnderChest().onInitialize();
if(getConfig().veinMinerEnabled) {
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
if (player instanceof ServerPlayerEntity serverPlayer) {
return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state);
}
else {
return true;
}
});
ServerEntityEvents.ENTITY_LOAD.register(VeinMinerEvents::onEntityLoad);
}
}
}

View File

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

View File

@ -0,0 +1,40 @@
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.CheckedRandom;
import net.minecraft.util.math.random.ChunkRandom;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.chunk.Chunk;
import wtf.hak.survivalfabric.config.ConfigManager;
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;
}
}

View File

@ -8,7 +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.DimensionTeams;
import wtf.hak.survivalfabric.utils.Utils;
import java.util.HashMap;
import java.util.Map;
@ -20,23 +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);
DimensionTeams.assignCorrectTeam(player, data.world.getRegistryKey().getValue().toTranslationKey());
Utils.updateListNames(player);
} else {
spectating.put(player, new LocationData(player
@ -47,7 +47,7 @@ public class SpectatorCommand {
.getPitch(), player
.getServerWorld()));
player.changeGameMode(GameMode.SPECTATOR);
DimensionTeams.assignCorrectTeam(player, "");
Utils.updateListNames(player);
}
return 1;
}

View File

@ -0,0 +1,52 @@
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.0";
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 = true;
public List<String> sharedEnderChestNames = Lists.newArrayList("AlwaysHAK", "LunaticFox");
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 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;
};
}
}

View File

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

View File

@ -1,16 +1,14 @@
package wtf.hak.survivalfabric.mixin;
import com.mojang.authlib.minecraft.client.MinecraftClient;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.message.MessageType;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
import net.minecraft.server.MinecraftServer;
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;
@ -18,9 +16,9 @@ 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.DimensionTeams;
import wtf.hak.survivalfabric.utils.Messages;
import wtf.hak.survivalfabric.config.ConfigManager;
import java.util.Objects;
import java.util.Set;
@Mixin(PlayerManager.class)
@ -28,19 +26,19 @@ public abstract class PlayerManagerMixin {
@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);
}
@Inject(method = {"onPlayerConnect"}, at = {@At(value = "HEAD")})
public void onPlayerConnectHead(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
DimensionTeams.assignCorrectTeam(player, player.getServerWorld().getRegistryKey().getValue().toTranslationKey());
if(ConfigManager.getConfig().joinMessageEnabled && !player.getServer().isSingleplayer()) {
Text text = Text.literal(String.format(ConfigManager.getConfig().joinMessage, player.getName().getString()));
player.sendMessage(text, false);
}
}
@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;
}
@Inject(method = {"remove"}, at = {@At("HEAD")})
@ -54,9 +52,9 @@ public abstract class PlayerManagerMixin {
@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 && ConfigManager.getConfig().chatMessageEnabled) {
Text text = Text.literal(String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), message.getContent().getString()));
Objects.requireNonNull(sender.getServer()).getPlayerManager().broadcast(text, false);
ci.cancel();
}
}

View File

@ -5,14 +5,18 @@ 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 {
@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;
}
}

View File

@ -0,0 +1,36 @@
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 abstract class ServerPlayerEntityMixin {
@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));
}
}
}

View File

@ -3,13 +3,11 @@ package wtf.hak.survivalfabric.mixin;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
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.CallbackInfo;
import wtf.hak.survivalfabric.utils.DimensionTeams;
import wtf.hak.survivalfabric.utils.Messages;
import wtf.hak.survivalfabric.utils.Utils;
@Mixin(ServerWorld.class)
public class ServerWorldMixin {
@ -17,10 +15,7 @@ public class ServerWorldMixin {
@Inject(method = "onDimensionChanged", at = {@At("HEAD")})
public void onDimensionChange(Entity entity, CallbackInfo ci) {
if(entity instanceof ServerPlayerEntity) {
ServerPlayerEntity player = (ServerPlayerEntity)entity;
String dimension = Messages.getDimensionFormatted(player.getServerWorld().getRegistryKey().getValue().toTranslationKey());
player.sendMessage(Text.literal(dimension), false);
DimensionTeams.assignCorrectTeam(player, player.getServerWorld().getRegistryKey().getValue().toTranslationKey());
Utils.updateListNames((ServerPlayerEntity)entity);
}
}
}

View File

@ -0,0 +1,129 @@
package wtf.hak.survivalfabric.sharedenderchest;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
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.*;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
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 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 static void saveInventory(MinecraftServer server) {
File inventoryFile = getFile(server);
NbtCompound nbt = new NbtCompound();
DefaultedList<ItemStack> inventoryItemStacks = DefaultedList.ofSize(getConfig().sharedEnderChestRows * 9, ItemStack.EMPTY);
Inventories.writeNbt(nbt, sharedInventory.getList(inventoryItemStacks), server.getRegistryManager());
try (FileOutputStream inventoryFileOutputStream = new FileOutputStream(inventoryFile);
DataOutputStream inventoryFileDataOutput = new DataOutputStream(inventoryFileOutputStream)) {
inventoryFile.createNewFile();
NbtIo.writeCompressed(nbt, inventoryFileDataOutput);
} catch (Exception e) {
LOGGER.error("Error while saving Shared Ender Chest: " + e);
}
}
public void onServerStopping(MinecraftServer server) {
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;
playEnderChestOpenSound(world, hitResult.getBlockPos());
openSharedEnderChest(player);
return ActionResult.SUCCESS;
} else {
for(String name : getConfig().sharedEnderChestNames) {
if(name.toLowerCase().strip().equalsIgnoreCase(player.getNameForScoreboard().toLowerCase())) {
if (world.isClient()) return ActionResult.SUCCESS;
playEnderChestOpenSound(world, hitResult.getBlockPos());
openSharedEnderChest(player);
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);
}
public static void openSharedEnderChest(PlayerEntity player) {
player.openHandledScreen(new SimpleNamedScreenHandlerFactory((int_1, playerInventory, playerEntity) ->
new GenericContainerScreenHandler(getConfig().screenHandlerType(), int_1, playerInventory, sharedInventory, getConfig().sharedEnderChestRows), Text.of(getConfig().sharedEnderChestName)));
}
public static void playEnderChestOpenSound(World world, BlockPos pos) {
world.playSound(null, pos, SoundEvents.BLOCK_ENDER_CHEST_OPEN, SoundCategory.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
}
private static File getFile(MinecraftServer server) {
return server.getSavePath(WorldSavePath.ROOT).resolve("sharedenderchest.sav").toFile();
}
}

View File

@ -0,0 +1,83 @@
package wtf.hak.survivalfabric.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;
public class SharedInventory implements Inventory {
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) {
ItemStack itemStack_1 = Inventories.splitStack(this.stacks, int_1, int_2);
return itemStack_1;
}
@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();
}
}

View File

@ -1,108 +0,0 @@
package wtf.hak.survivalfabric.utils;
import net.minecraft.scoreboard.ServerScoreboard;
import net.minecraft.scoreboard.Team;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import wtf.hak.survivalfabric.commands.SpectatorCommand;
public class DimensionTeams {
public static DimensionTeams INSTANCE;
public Team overworldTeam;
public Team netherTeam;
public Team endTeam;
public Team spectatorTeam;
public Team unknownTeam;
public DimensionTeams(ServerPlayerEntity player) {
ServerScoreboard scoreboard = player.getServer().getScoreboard();
for(Team team : scoreboard.getTeams()) {
if(team.getName().equals("survivalfabric_overworld")) {
player.getServer().getPlayerManager().broadcast(Text.literal("found old"), false);
overworldTeam = team;
} else if(team.getName().equals("survivalfabric_nether")) {
player.getServer().getPlayerManager().broadcast(Text.literal("found old"), false);
netherTeam = team;
} else if(team.getName().equals("survivalfabric_end")) {
player.getServer().getPlayerManager().broadcast(Text.literal("found old"), false);
endTeam = team;
} else if(team.getName().equals("survivalfabric_spectator")) {
player.getServer().getPlayerManager().broadcast(Text.literal("found old"), false);
spectatorTeam = team;
} else if(team.getName().equals("survivalfabric_unknown")) {
player.getServer().getPlayerManager().broadcast(Text.literal("found old"), false);
unknownTeam = team;
}
}
if(overworldTeam == null) {
overworldTeam = scoreboard.addTeam("survivalfabric_overworld");
player.getServer().getPlayerManager().broadcast(Text.literal("added new"), false);
} else if (netherTeam == null) {
netherTeam = scoreboard.addTeam("survivalfabric_nether");
player.getServer().getPlayerManager().broadcast(Text.literal("added new"), false);
} else if (endTeam == null) {
endTeam = scoreboard.addTeam("survivalfabric_end");
player.getServer().getPlayerManager().broadcast(Text.literal("added new"), false);
} else if (spectatorTeam == null) {
spectatorTeam = scoreboard.addTeam("survivalfabric_spectator");
player.getServer().getPlayerManager().broadcast(Text.literal("added new"), false);
} else if (unknownTeam == null) {
unknownTeam = scoreboard.addTeam("survivalfabric_unknown");
player.getServer().getPlayerManager().broadcast(Text.literal("added new"), false);
}
overworldTeam.setPrefix(Text.literal(Messages.OVERWORLD_PREFIX));
overworldTeam.setColor(Formatting.GRAY);
netherTeam.setPrefix(Text.literal(Messages.NETHER_PREFIX));
netherTeam.setColor(Formatting.GRAY);
endTeam.setPrefix(Text.literal(Messages.END_PREFIX));
endTeam.setColor(Formatting.GRAY);
spectatorTeam.setPrefix(Text.literal(Messages.SPECTATOR_PREFIX));
spectatorTeam.setColor(Formatting.GRAY);
unknownTeam.setPrefix(Text.literal(Messages.UNKNOWN_PREFIX));
unknownTeam.setColor(Formatting.GRAY);
}
public static Team assignCorrectTeam(ServerPlayerEntity player, String translationKey) {
if (INSTANCE == null) {
INSTANCE = new DimensionTeams(player);
}
ServerScoreboard scoreboard = player.getServer().getScoreboard();
String name = player.getNameForScoreboard();
for(Team team : player.getServer().getScoreboard().getTeams()) {
if(team.getName().startsWith("survivalfabric")) {
if(team.getPlayerList().contains(name))
scoreboard.removeScoreHolderFromTeam(name, team);
}
}
if(!SpectatorCommand.spectating.containsKey(player)) {
switch (translationKey) {
case "minecraft.overworld":
scoreboard.addScoreHolderToTeam(name, INSTANCE.overworldTeam);
return INSTANCE.overworldTeam;
case "minecraft.the_nether":
scoreboard.addScoreHolderToTeam(name, INSTANCE.netherTeam);
return INSTANCE.netherTeam;
case "minecraft.the_end":
scoreboard.addScoreHolderToTeam(name, INSTANCE.endTeam);
return INSTANCE.endTeam;
default:
scoreboard.addScoreHolderToTeam(name, INSTANCE.unknownTeam);
return INSTANCE.unknownTeam;
}
} else {
scoreboard.addScoreHolderToTeam(name, INSTANCE.spectatorTeam);
return INSTANCE.spectatorTeam;
}
}
}

View File

@ -1,28 +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";
public static final String OVERWORLD_PREFIX = "§8[§aOverworld§8] ";
public static final String NETHER_PREFIX = "§8[§cNether§8] ";
public static final String END_PREFIX = "§8[§dEnd§8] ";
public static final String SPECTATOR_PREFIX = "§8[§eSpectator§8] ";
public static final String UNKNOWN_PREFIX = "§8[§7Unknown§8] ";
public static String getDimensionFormatted(String translationKey) {
switch(translationKey) {
case "minecraft.overworld":
return "Overworld";
case "minecraft.the_nether":
return "Nether";
case "minecraft.the_end":
return "End";
default:
return "Unknown";
}
}
}

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

View File

@ -0,0 +1,10 @@
package wtf.hak.survivalfabric.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);
}

View File

@ -0,0 +1,59 @@
package wtf.hak.survivalfabric.veinminer;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.registry.Registries;
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.veinminer.drills.LeavesDrill;
import wtf.hak.survivalfabric.veinminer.drills.OreDrill;
import wtf.hak.survivalfabric.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;
}
}

View File

@ -0,0 +1,66 @@
package wtf.hak.survivalfabric.veinminer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
public class VeinMinerSession {
private static ArrayList<VeinMinerSession> sessions = new ArrayList<>();
public ServerPlayerEntity player;
public ServerWorld world;
public Set<BlockPos> positions;
public BlockPos 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);
}
private VeinMinerSession(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) {
this.player = player;
this.world = world;
this.initialPos = initialPos;
this.positions = new HashSet<>();
positions.add(initialPos);
}
public void addPosition(BlockPos pos) {
positions.add(pos);
}
public void removePosition(BlockPos pos) {
positions.remove(pos);
}
public void finish() {
finish(this);
}
}

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,19 @@
{
"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"
}

View File

@ -0,0 +1,5 @@
{
"values": [
"#minecraft:leaves"
]
}

View 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"
]
}

View File

@ -0,0 +1,5 @@
{
"values": [
"#minecraft:logs"
]
}

View File

@ -18,12 +18,19 @@
"main": [
"wtf.hak.survivalfabric.SurvivalFabric"
],
"client": [
"wtf.hak.survivalfabric.SurvivalFabricClient"
],
"fabric-datagen": [
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
]
},
"mixins": [
"survivalfabric.mixins.json"
"survivalfabric.mixins.json",
{
"config": "survivalfabric.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.16.10",
@ -32,6 +39,6 @@
"fabric-api": "*"
},
"suggests": {
"another-mod": "*"
}
}

View File

@ -1,13 +1,14 @@
{
"required": true,
"package": "wtf.hak.survivalfabric.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"PlayerManagerMixin",
"ServerPlayNetworkHandlerMixin",
"ServerWorldMixin"
],
"injectors": {
"defaultRequire": 1
"required": true,
"package": "wtf.hak.survivalfabric.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"PlayerManagerMixin",
"ServerPlayerEntityMixin",
"ServerPlayNetworkHandlerMixin",
"ServerWorldMixin"
],
"injectors": {
"defaultRequire": 1
}
}