22 Commits

Author SHA1 Message Date
ba6153fa41 Updated README.md
All checks were successful
build / build (push) Successful in 1m15s
2025-04-12 17:29:40 +02:00
798027a3af Updated mod version 2025-04-12 17:19:40 +02:00
6b2d080746 Made teleport head lock toggleable 2025-04-12 16:59:39 +02:00
87b6900ff3 Cleaned up 2025-04-12 16:59:27 +02:00
c39249e3c1 Optimized chat message system 2025-04-12 16:31:45 +02:00
281297bdca Cleaned up
All checks were successful
build / build (push) Successful in 1m13s
2025-04-12 16:21:27 +02:00
55e9b0fafd Added /camera keybind 2025-04-12 16:19:59 +02:00
4b5b697883 Added color to Chat Calc answer & made optional to add an equals sign 2025-04-12 16:19:36 +02:00
9325e24c5c Removed unnecessary comment
All checks were successful
build / build (push) Successful in 1m16s
2025-04-12 15:21:21 +02:00
02d2f624cf Code cleanup 2025-04-11 17:30:08 +02:00
9041bacf48 Updated README.md 2025-04-11 17:21:59 +02:00
23aefb2f8d Code cleanup 2025-04-11 17:21:51 +02:00
0941d3929e Renamed Utils.java to PacketUtils.java 2025-04-11 14:18:38 +02:00
c7722115ed Fixed bug where EC closes if another player is still inside 2025-04-11 14:17:14 +02:00
168f916baa Moved features to "features" package 2025-04-11 14:07:19 +02:00
f28159b442 Open/close EC block while opening/closing SEC + Play open & close sounds 2025-04-11 14:06:21 +02:00
c3a13c8063 Optimized fog removal ever so slightly 2025-04-11 13:21:46 +02:00
6b806574ac Removed Darkness Effect (toggleable)
All checks were successful
build / build (push) Successful in 1m19s
2025-04-11 13:17:19 +02:00
ddd5b9bf75 Fixed typo
All checks were successful
build / build (push) Successful in 1m12s
2025-04-07 10:37:44 +02:00
8c3798d456 Created basic Mod Menu Integration and made different fog types toggleable 2025-04-07 10:32:39 +02:00
d99467d953 Added ModMenu as (optional) dependency 2025-04-07 09:18:14 +02:00
7d969d0013 Removed Game Fog (cleaned up AngleViewer code)
All checks were successful
build / build (push) Successful in 1m2s
2025-04-06 12:25:30 +02:00
36 changed files with 592 additions and 290 deletions

View File

@ -3,7 +3,7 @@
# ![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 ;) )
As a challenge I'm trying to make it as user-friendly as possible.
# Current feature-set
## Server Side
@ -26,25 +26,35 @@ As a challenge I'm trying to make it as user-friendly as possible. (It ain't the
- Version "control"
- Shared Ender Chest
- Shared EC Access control (via config)
- Open/close EC block while opening/closing SEC
- Play open & close sounds
- Vein miner
![VeinMiner](https://i.imgur.com/zOXWMNa.gif)
- Chat Calculator
### 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
# Currently working on 1.3.2
## Server Side
- [x] Chat Calculator
## Client Side
- [x] Teleportation Angle Viewer
- Teleportation Angle Viewer for [this machine](https://www.youtube.com/watch?v=FnUE-ZaALLw)
- Toggleable pitch/yaw lock while teleporting
![Teleportation Keybindings](https://i.imgur.com/gjO1H3d.png)
- Remove game fog (lava, water, etc.)
- All types individually toggleable
- Mod Menu integration
- Automatic config adaption (currently booleans only)
- Remove darkness effect
- Toggleable
- Keybinding for /camera
# Features to come
# To-do
## General
- Rework config system
- Store server settings in world folder for better singleplayer use
- Rework Mod Menu integration to be more flexible
## Server Side
- Telekinesis

View File

@ -11,11 +11,10 @@ base {
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
maven {
name = "Terraformers"
url = "https://maven.terraformersmc.com/"
}
}
loom {
@ -44,7 +43,7 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation("com.terraformersmc:modmenu:${project.modmenu_version}")
}
processResources {

View File

@ -9,9 +9,11 @@ yarn_mappings=1.21.5+build.1
loader_version=0.16.10
# Mod Properties
mod_version=1.3.2
mod_version=1.4.0
maven_group=wtf.hak.survivalfabric
archives_base_name=survivalfabric
# Dependencies
fabric_version=0.119.5+1.21.5
modmenu_version=14.0.0-rc.2

View File

@ -1,12 +1,23 @@
package wtf.hak.survivalfabric;
import net.fabricmc.api.ClientModInitializer;
import wtf.hak.survivalfabric.teleportation.AngleViewHandler;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
import wtf.hak.survivalfabric.features.AngleViewer;
import wtf.hak.survivalfabric.features.RemoveDarknessEffect;
import wtf.hak.survivalfabric.features.SFKeyBindings;
public class SurvivalFabricClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
AngleViewHandler.registerKeybindings();
// Config
ClientConfigManager.getConfig();
// Features
AngleViewer.register();
RemoveDarknessEffect.register();
SFKeyBindings.register();
}
}

View File

@ -0,0 +1,14 @@
package wtf.hak.survivalfabric.config.client;
public class ClientConfig {
public String configVersion = "1.0";
public boolean renderNetherFog = false;
public boolean renderOverworldFog = false;
public boolean renderEndFog = false;
public boolean renderLavaFog = false;
public boolean renderWaterFog = false;
public boolean renderSnowFog = false;
public boolean removeDarknessEffect = true;
public boolean lockTeleportHeadMovement = true;
}

View File

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

View File

@ -1,4 +1,4 @@
package wtf.hak.survivalfabric.teleportation;
package wtf.hak.survivalfabric.features;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
@ -11,12 +11,12 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AngleViewHandler {
public class AngleViewer {
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public static boolean PREVENT_HEAD_MOVEMENT = false;
public static void registerKeybindings() {
public static void register() {
for (Angle angle : Angle.values()) {
KeyBinding keyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key.survivalfabric." + angle.name().toLowerCase(),

View File

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

View File

@ -0,0 +1,22 @@
package wtf.hak.survivalfabric.features;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;
public class SFKeyBindings {
private static final KeyBinding CAMERA_BIND = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.survivalfabric.camera", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_PERIOD, "category.survivalfabric.survivalfabric"));
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if(client.player != null) {
if (CAMERA_BIND.wasPressed()) {
client.player.networkHandler.sendChatCommand("camera");
}
}
});
}
}

View File

@ -0,0 +1,47 @@
package wtf.hak.survivalfabric.mixin.client;
import net.minecraft.block.enums.CameraSubmersionType;
import net.minecraft.client.render.BackgroundRenderer;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Fog;
import net.minecraft.client.render.FogShape;
import net.minecraft.world.World;
import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static wtf.hak.survivalfabric.config.client.ClientConfigManager.getConfig;
@Mixin(value = BackgroundRenderer.class, priority = 910)
public abstract class BackgroundRendererMixin {
@Unique
private static final Fog EMPTY_FOG = new Fog(-8.0f, 1_000_000.0F, FogShape.CYLINDER, 0, 0, 0, 0);
@Inject(method = "applyFog", at = @At("RETURN"), cancellable = true)
private static void applyFog(Camera camera, BackgroundRenderer.FogType fogType, Vector4f color, float viewDistance, boolean thickenFog, float tickProgress, CallbackInfoReturnable<Fog> cir) {
CameraSubmersionType submersion = camera.getSubmersionType();
boolean renderFog = true;
switch (submersion) {
case NONE -> {
World world = camera.getFocusedEntity().getWorld();
if ((world.getRegistryKey() == World.OVERWORLD && !getConfig().renderOverworldFog)
|| (world.getRegistryKey() == World.NETHER && !getConfig().renderNetherFog)
|| (world.getRegistryKey() == World.END && !getConfig().renderEndFog)) {
renderFog = false;
}
}
case WATER -> renderFog = getConfig().renderWaterFog;
case LAVA -> renderFog = getConfig().renderLavaFog;
case POWDER_SNOW -> renderFog = getConfig().renderSnowFog;
}
if (!renderFog) {
cir.setReturnValue(EMPTY_FOG);
}
}
}

View File

@ -6,7 +6,8 @@ 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;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
import wtf.hak.survivalfabric.features.AngleViewer;
@Mixin(Entity.class)
public abstract class EntityMixin {
@ -14,7 +15,7 @@ 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) {
if (player.isMainPlayer() && AngleViewer.PREVENT_HEAD_MOVEMENT && ClientConfigManager.getConfig().lockTeleportHeadMovement) {
ci.cancel();
}
}
@ -23,7 +24,7 @@ public abstract class EntityMixin {
@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) {
if (player.isMainPlayer() && AngleViewer.PREVENT_HEAD_MOVEMENT && ClientConfigManager.getConfig().lockTeleportHeadMovement) {
ci.cancel();
}
}

View File

@ -0,0 +1,87 @@
package wtf.hak.survivalfabric.modmenu;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.text.Text;
import wtf.hak.survivalfabric.config.client.ClientConfig;
import wtf.hak.survivalfabric.config.client.ClientConfigManager;
import java.lang.reflect.Field;
public class ConfigScreen extends Screen {
private final Screen parent;
public ConfigScreen(Screen parent) {
super(Text.literal("Survival Fabric - Client Config"));
this.parent = parent;
}
@Override
protected void init() {
int buttonWidth = 200;
int buttonHeight = 20;
int spacing = 5;
int startY = 40;
int i = 0;
for (Field field : ClientConfig.class.getFields()) {
if (field.getType() == boolean.class) {
int y = startY + i * (buttonHeight + spacing);
try {
boolean value = field.getBoolean(ClientConfigManager.getConfig());
String label = formatFieldName(field.getName()) + ": " + (value ? "ON" : "OFF");
ButtonWidget button = ButtonWidget.builder(
Text.literal(label),
b -> {
try {
boolean current = field.getBoolean(ClientConfigManager.getConfig());
field.setBoolean(ClientConfigManager.getConfig(), !current);
b.setMessage(Text.literal(formatFieldName(field.getName()) + ": " + (!current ? "ON" : "OFF")));
ClientConfigManager.save(); // Save if needed
} catch (Exception e) {
e.printStackTrace();
}
}
).dimensions(this.width / 2 - buttonWidth / 2, y, buttonWidth, buttonHeight).build();
this.addDrawableChild(button);
i++;
} catch (Exception e) {
e.printStackTrace();
}
}
}
this.addDrawableChild(ButtonWidget.builder(
Text.translatable("gui.done"),
button -> this.client.setScreen(parent)
).dimensions(this.width / 2 - 75, startY + i * (buttonHeight + spacing) + 10, 150, 20).build());
}
@Override
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
context.fill(0, 0, this.width, this.height, 0xC0101010);
int titleX = (this.width / 2) - (this.textRenderer.getWidth(this.title) / 2);
context.drawTextWithShadow(this.textRenderer, this.title, titleX, 20, 0xFFFFFF);
super.render(context, mouseX, mouseY, delta);
}
private String formatFieldName(String rawName) {
StringBuilder result = new StringBuilder();
char[] chars = rawName.toCharArray();
result.append(Character.toUpperCase(chars[0]));
for (int i = 1; i < chars.length; i++) {
if (Character.isUpperCase(chars[i])) {
result.append(' ');
}
result.append(chars[i]);
}
return result.toString();
}
}

View File

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

View File

@ -3,6 +3,7 @@
"package": "wtf.hak.survivalfabric.mixin.client",
"compatibilityLevel": "JAVA_21",
"client": [
"BackgroundRendererMixin",
"EntityMixin"
],
"injectors": {

View File

@ -1,7 +1,6 @@
package wtf.hak.survivalfabric;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
@ -11,8 +10,8 @@ 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 wtf.hak.survivalfabric.features.sharedenderchest.SharedEnderChest;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerEvents;
import static wtf.hak.survivalfabric.config.ConfigManager.getConfig;
@ -24,9 +23,9 @@ 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" }));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SpectatorCommand.register(dispatcher, "spectator", "s", "S", "camera", "c", "C"));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> ReloadConfigCommand.register(dispatcher, "reloadsurvivalconfig"));
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> SlimeChunkCommand.register(dispatcher, "slimechunk", "sc"));
if (getConfig().sharedEnderChestEnabled)
new SharedEnderChest().onInitialize();
@ -35,8 +34,7 @@ public class SurvivalFabric implements ModInitializer {
PlayerBlockBreakEvents.BEFORE.register((world, player, pos, state, blockEntity) -> {
if (player instanceof ServerPlayerEntity serverPlayer) {
return VeinMinerEvents.beforeBlockBreak(world, serverPlayer, pos, state);
}
else {
} else {
return true;
}
});

View File

@ -6,11 +6,9 @@ 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;

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.Utils;
import wtf.hak.survivalfabric.utils.PacketUtils;
import java.util.HashMap;
import java.util.Map;
@ -36,7 +36,7 @@ public class SpectatorCommand {
player.teleport(data.world, data.x, data.y, data.z, Set.of(), data.yaw, data.pitch, false);
player.changeGameMode(GameMode.SURVIVAL);
spectating.remove(player);
Utils.updateListNames(player);
PacketUtils.updateListNames(player);
} else {
spectating.put(player, new LocationData(player
@ -47,7 +47,7 @@ public class SpectatorCommand {
.getPitch(), player
.getServerWorld()));
player.changeGameMode(GameMode.SPECTATOR);
Utils.updateListNames(player);
PacketUtils.updateListNames(player);
}
return 1;
}

View File

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

View File

@ -1,13 +1,18 @@
package wtf.hak.survivalfabric.sharedenderchest;
package wtf.hak.survivalfabric.features.sharedenderchest;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventories;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import java.util.HashMap;
import java.util.Map;
public class SharedInventory implements Inventory {
public final Map<PlayerEntity, BlockPos> openedEnderChests = new HashMap<>();
private final DefaultedList<ItemStack> stacks;
public SharedInventory(int inventoryRows) {
@ -52,8 +57,7 @@ public class SharedInventory implements Inventory {
@Override
public ItemStack removeStack(int int_1, int int_2) {
ItemStack itemStack_1 = Inventories.splitStack(this.stacks, int_1, int_2);
return itemStack_1;
return Inventories.splitStack(this.stacks, int_1, int_2);
}
@Override
@ -80,4 +84,12 @@ public class SharedInventory implements Inventory {
public void clear() {
stacks.clear();
}
@Override
public void onClose(PlayerEntity player) {
BlockPos pos = openedEnderChests.remove(player);
if (openedEnderChests.containsValue(pos))
return;
SharedEnderChest.fakeEnderChestOpen(player.getWorld(), pos, false);
}
}

View File

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

View File

@ -1,15 +1,14 @@
package wtf.hak.survivalfabric.veinminer;
package wtf.hak.survivalfabric.features.veinminer;
import net.minecraft.block.BlockState;
import net.minecraft.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;
import wtf.hak.survivalfabric.features.veinminer.drills.LeavesDrill;
import wtf.hak.survivalfabric.features.veinminer.drills.OreDrill;
import wtf.hak.survivalfabric.features.veinminer.drills.WoodDrill;
public class VeinMinerEvents {
@ -25,8 +24,7 @@ public class VeinMinerEvents {
boolean shouldContinue = !mine(session);
session.finish();
return shouldContinue;
}
else {
} else {
return true;
}
}

View File

@ -1,21 +1,29 @@
package wtf.hak.survivalfabric.veinminer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
package wtf.hak.survivalfabric.features.veinminer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class VeinMinerSession {
private static ArrayList<VeinMinerSession> sessions = new ArrayList<>();
private static final ArrayList<VeinMinerSession> sessions = new ArrayList<>();
public ServerPlayerEntity player;
public ServerWorld world;
public Set<BlockPos> positions;
public BlockPos initialPos;
private VeinMinerSession(ServerPlayerEntity player, ServerWorld world, BlockPos initialPos) {
this.player = player;
this.world = world;
this.initialPos = initialPos;
this.positions = new HashSet<>();
positions.add(initialPos);
}
public static VeinMinerSession sessionForPlayer(ServerPlayerEntity player) {
for (var session : sessions) {
if (session.player == player) {
@ -44,14 +52,6 @@ public class VeinMinerSession {
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);
}

View File

@ -1,15 +1,15 @@
package wtf.hak.survivalfabric.veinminer.drills;
package wtf.hak.survivalfabric.features.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 wtf.hak.survivalfabric.features.veinminer.Drill;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayList;
import static wtf.hak.survivalfabric.SurvivalFabric.LOGGER;
@ -37,14 +37,6 @@ public class DrillBase implements Drill {
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);
@ -75,20 +67,17 @@ public class DrillBase implements Drill {
String[] order = new String[]{"x", "y", "z"};
if (forceVertical) {
order = new String[]{"y", "x", "z"};
}
else {
} 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 {
} else {
order = new String[]{"y", "x", "z"};
}
}
else {
} else {
if (majorYawChange) {
order = new String[]{"z", "y", "x"};
}
@ -130,4 +119,12 @@ public class DrillBase implements Drill {
session.player.sendMessage(text);
LOGGER.info(message);
}
protected interface ForXYZHandler {
void handle(BlockPos pos);
}
protected interface ForXYZCounter {
int handle(BlockPos pos);
}
}

View File

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

View File

@ -1,4 +1,4 @@
package wtf.hak.survivalfabric.veinminer.drills;
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
@ -6,23 +6,21 @@ import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import 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 static final TagKey<Block> oreTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "ore"));
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);

View File

@ -1,29 +1,27 @@
package wtf.hak.survivalfabric.veinminer.drills;
package wtf.hak.survivalfabric.features.veinminer.drills;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import wtf.hak.survivalfabric.features.veinminer.VeinMinerSession;
import java.util.ArrayDeque;
import 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 static final TagKey<Block> woodTag = TagKey.of(RegistryKeys.BLOCK, Identifier.of("survivalfabric", "wood"));
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);
@ -33,8 +31,8 @@ public class WoodDrill extends DrillBase {
public boolean drill(BlockPos startPos) {
ServerWorld world = session.world;
int broken = 0;
ArrayDeque<BlockPos> pendingLogs = new ArrayDeque<BlockPos>();
ArrayDeque<BlockPos> logBlocks = new ArrayDeque<BlockPos>();
ArrayDeque<BlockPos> pendingLogs = new ArrayDeque<>();
ArrayDeque<BlockPos> logBlocks = new ArrayDeque<>();
pendingLogs.add(startPos);
String leavesBlockId = Registries.BLOCK.getId(world.getBlockState(startPos).getBlock()).toString().replace("_log", "_leaves");
@ -57,11 +55,8 @@ public class WoodDrill extends DrillBase {
}
}
// 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();

View File

@ -1,10 +1,8 @@
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.server.MinecraftServer;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ConnectedClientData;
import net.minecraft.server.network.ServerPlayerEntity;
@ -18,9 +16,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import wtf.hak.survivalfabric.commands.SpectatorCommand;
import wtf.hak.survivalfabric.config.ConfigManager;
import java.awt.*;
import java.beans.Expression;
import java.util.Objects;
import java.util.Set;
@Mixin(PlayerManager.class)
@ -54,33 +49,39 @@ 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 && ConfigManager.getConfig().chatMessageEnabled) {
if (sender != null) {
String rawMessage = message.getContent().getString().trim();
if(sender != null && ConfigManager.getConfig().chatCalcEnabled && rawMessage.endsWith("=")) {
String expression = rawMessage.substring(0, rawMessage.length() - 1).trim();
boolean isCalcEnabled = ConfigManager.getConfig().chatCalcEnabled;
boolean isMsgEnabled = ConfigManager.getConfig().chatMessageEnabled;
String processedMessage = rawMessage;
if (isCalcEnabled) {
String expression = rawMessage.endsWith("=") ? rawMessage.substring(0, rawMessage.length() - 1).trim() : rawMessage;
try {
String result = String.valueOf(evaluateExpression(expression));
if(rawMessage.contains(" ")) rawMessage += " ";
rawMessage += (result.endsWith(".0")) ? result.substring(0, result.length() - 2) : result;
} catch (Exception e) {}
StringBuilder sb = new StringBuilder(rawMessage).append("§6");
if (rawMessage.contains(" ")) sb.append(" ");
if (!rawMessage.endsWith("=")) sb.append("=");
if (rawMessage.contains(" ")) sb.append(" ");
sb.append(result.endsWith(".0") ? result.substring(0, result.length() - 2) : result);
processedMessage = sb.toString();
} catch (Exception ignored) {}
}
Text text = Text.literal(String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), rawMessage));
sender.getServer().getPlayerManager().broadcast(text, false);
if (isMsgEnabled) {
String formatted = String.format(ConfigManager.getConfig().chatMessage, sender.getName().getString(), processedMessage);
sender.getServer().getPlayerManager().broadcast(Text.literal(formatted), false);
} else if (isCalcEnabled) {
String formatted = "<" + sender.getName().getString() + "> " + processedMessage;
sender.getServer().getPlayerManager().broadcast(Text.literal(formatted), false);
}
ci.cancel();
} else if (sender != null && ConfigManager.getConfig().chatCalcEnabled) {
String rawMessage = message.getContent().getString().trim();
if (rawMessage.endsWith("=")) {
String expression = rawMessage.substring(0, rawMessage.length() - 1).trim();
try {
String result = String.valueOf(evaluateExpression(expression));
if(rawMessage.contains(" ")) rawMessage += " ";
rawMessage += (result.endsWith(".0")) ? result.substring(0, result.length() - 2) : result;
Text formattedMessage = Text.literal("<" + sender.getName().getString() + "> " + rawMessage);
sender.getServer().getPlayerManager().broadcast(formattedMessage, false);
ci.cancel();
} catch (Exception e) {}
}
}
}
private double evaluateExpression(String expression) {

View File

@ -7,7 +7,7 @@ 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.Utils;
import wtf.hak.survivalfabric.utils.PacketUtils;
@Mixin(ServerWorld.class)
public class ServerWorldMixin {
@ -15,7 +15,7 @@ public class ServerWorldMixin {
@Inject(method = "onDimensionChanged", at = {@At("HEAD")})
public void onDimensionChange(Entity entity, CallbackInfo ci) {
if (entity instanceof ServerPlayerEntity) {
Utils.updateListNames((ServerPlayerEntity)entity);
PacketUtils.updateListNames((ServerPlayerEntity) entity);
}
}
}

View File

@ -5,7 +5,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
import java.util.Objects;
public class Utils {
public class PacketUtils {
public static void updateListNames(ServerPlayerEntity p) {
for (ServerPlayerEntity sp : Objects.requireNonNull(p.getServer()).getPlayerManager().getPlayerList()) {

View File

@ -15,5 +15,7 @@
"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"
"key.survivalfabric.angle15": "244.97 / -41.71",
"category.survivalfabric.survivalfabric": "Survival Fabric",
"key.survivalfabric.camera": "/camera"
}

View File

@ -9,7 +9,8 @@
],
"contact": {
"homepage": "https://hak.wtf",
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric"
"sources": "https://git.hak.wtf/hkuijlman/SurvivalFabric",
"issues": "https://git.hak.wtf/hkuijlman/SurvivalFabric/issues"
},
"license": "CC0-1.0",
"icon": "assets/survivalfabric/icon.png",
@ -23,6 +24,9 @@
],
"fabric-datagen": [
"wtf.hak.survivalfabric.SurvivalFabricDataGenerator"
],
"modmenu": [
"wtf.hak.survivalfabric.modmenu.ModMenuIntegration"
]
},
"mixins": [
@ -38,7 +42,7 @@
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
"another-mod": "*"
"optional": {
"modmenu": "*"
}
}