16 Commits

Author SHA1 Message Date
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
24 changed files with 976 additions and 60 deletions

View File

@ -1,24 +1,43 @@
# Survival Fabric
# ![Logo](https://i.imgur.com/ktkHND1.png)
This mod is FAR FROM FINISHED, and initially just created for our private Survival Server. This mod is FAR FROM FINISHED, and initially just created for our private Survival Server.
As a challenge I'm trying to make it as user-friendly as possible. (It ain't there yet tho ;) ) As a challenge I'm trying to make it as user-friendly as possible. (It ain't there yet tho ;) )
## Current feature-set ## Current feature-set
### Features ### Features
- Custom join message - Custom join message
- Custom quit message ![Join Message](https://i.imgur.com/7uv5lUb.png)
- Custom chat message - Custom quit message
- Tablist dimension indicator ![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 ### Commands
- /spectator | Essentially server-side freecam, you get put in spectator and are able to fly around, once you use the command again you get put back to where you were. - /spectator | Essentially server-side free-cam, you get put in spectator and are able to fly around, once you use the command again you get put back to where you were.
- /slimechunk (/sc) | See if you're currently in a slimechunk
### Misc
- Updated icon
## Features to come ## Features to come
### Features
- Vein miner
- Telekinesis - Telekinesis
- Shared Enderchest
- [ ] Shared for all Other than that no more features!
- [ ] Shared per team 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

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

View File

@ -3,11 +3,18 @@ package wtf.hak.survivalfabric;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.minecraft.scoreboard.Team; import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.minecraft.server.network.ServerPlayerEntity;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.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 { public class SurvivalFabric implements ModInitializer {
@ -18,6 +25,23 @@ public class SurvivalFabric implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, new String[] { "spectator", "s", "S", "camera", "c", "C", })); 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

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

@ -14,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import wtf.hak.survivalfabric.commands.SpectatorCommand; import wtf.hak.survivalfabric.commands.SpectatorCommand;
import wtf.hak.survivalfabric.utils.Messages; import wtf.hak.survivalfabric.config.ConfigManager;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -24,14 +24,19 @@ public abstract class PlayerManagerMixin {
@Inject(method = {"onPlayerConnect"}, at = {@At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")}) @Inject(method = {"onPlayerConnect"}, at = {@At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")})
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) { public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
Text text = Text.literal(String.format(Messages.JOIN_MESSAGE, player.getName().getString())); if(ConfigManager.getConfig().joinMessageEnabled) {
player.sendMessage(text, false); 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")) @ModifyArg(method = {"onPlayerConnect"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
private Text onPlayerConnect(Text text) { private Text onPlayerConnect(Text text) {
String name = text.getString().split(" ")[0]; if(ConfigManager.getConfig().joinMessageEnabled) {
return Text.literal(String.format(Messages.JOIN_MESSAGE, name)); String name = text.getString().split(" ")[0];
return Text.literal(String.format(ConfigManager.getConfig().joinMessage, name));
} else
return text;
} }
@Inject(method = {"remove"}, at = {@At("HEAD")}) @Inject(method = {"remove"}, at = {@At("HEAD")})
@ -45,8 +50,8 @@ 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) @Inject(method = {"broadcast(Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/network/message/MessageType$Parameters;)V"}, at = {@At("HEAD")}, cancellable = true)
private void onBroadcast(SignedMessage message, ServerPlayerEntity sender, MessageType.Parameters parameters, CallbackInfo ci) { private void onBroadcast(SignedMessage message, ServerPlayerEntity sender, MessageType.Parameters parameters, CallbackInfo ci) {
if(sender != null) { if(sender != null && ConfigManager.getConfig().chatMessageEnabled) {
Text text = Text.literal(String.format(Messages.CHAT_FORMAT, sender.getName().getString(), message.getContent().getString())); Text text = Text.literal(String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), message.getContent().getString()));
Objects.requireNonNull(sender.getServer()).getPlayerManager().broadcast(text, false); Objects.requireNonNull(sender.getServer()).getPlayerManager().broadcast(text, false);
ci.cancel(); ci.cancel();
} }

View File

@ -5,15 +5,18 @@ import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg; import org.spongepowered.asm.mixin.injection.ModifyArg;
import wtf.hak.survivalfabric.utils.Messages; import wtf.hak.survivalfabric.config.ConfigManager;
@Mixin(ServerPlayNetworkHandler.class) @Mixin(ServerPlayNetworkHandler.class)
public abstract class ServerPlayNetworkHandlerMixin { public abstract class ServerPlayNetworkHandlerMixin {
@ModifyArg(method = {"cleanUp"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V")) @ModifyArg(method = {"cleanUp"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V"))
private Text quitMessage(Text text) { private Text quitMessage(Text text) {
String name = text.getString().split(" ")[0]; if(ConfigManager.getConfig().quitMessageEnabled) {
return Text.literal(String.format(Messages.QUIT_MESSAGE, name)); String name = text.getString().split(" ")[0];
return Text.literal(String.format(ConfigManager.getConfig().quitMessage, name));
}
return text;
} }
} }

View File

@ -7,28 +7,30 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import wtf.hak.survivalfabric.commands.SpectatorCommand; import wtf.hak.survivalfabric.commands.SpectatorCommand;
import wtf.hak.survivalfabric.utils.Messages; import wtf.hak.survivalfabric.config.ConfigManager;
@Mixin(ServerPlayerEntity.class) @Mixin(ServerPlayerEntity.class)
public abstract class ServerPlayerEntityMixin { public abstract class ServerPlayerEntityMixin {
@Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true) @Inject(method = "getPlayerListName", at = @At("HEAD"), cancellable = true)
private void changePlayerListName(CallbackInfoReturnable<Text> cir) { private void changePlayerListName(CallbackInfoReturnable<Text> cir) {
ServerPlayerEntity p = (ServerPlayerEntity) (Object) this; if(ConfigManager.getConfig().dimensionIndicatorEnabled) {
String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey(); ServerPlayerEntity p = (ServerPlayerEntity) (Object) this;
String finalName; String world = p.getServerWorld().getRegistryKey().getValue().toTranslationKey();
if(!SpectatorCommand.spectating.containsKey(p)) { String finalName;
finalName = switch (world) { if (!SpectatorCommand.spectating.containsKey(p)) {
case "minecraft.overworld" -> Messages.OVERWORLD_PREFIX; finalName = switch (world) {
case "minecraft.the_nether" -> Messages.NETHER_PREFIX; case "minecraft.overworld" -> ConfigManager.getConfig().overworldPrefix;
case "minecraft.the_end" -> Messages.END_PREFIX; case "minecraft.the_nether" -> ConfigManager.getConfig().netherPrefix;
default -> Messages.UNKNOWN_PREFIX; case "minecraft.the_end" -> ConfigManager.getConfig().endPrefix;
}; default -> ConfigManager.getConfig().unknownPrefix;
} else };
finalName = Messages.SPECTATOR_PREFIX; } else
finalName = ConfigManager.getConfig().spectatorPrefix;
finalName += "§7" + p.getName().getString(); finalName += "§7" + p.getName().getString();
cir.setReturnValue(Text.of(finalName)); cir.setReturnValue(Text.of(finalName));
}
} }
} }

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,16 +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] ";
}

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,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:magma_block"
]
}

View File

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