Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ public static void registerCommands(CommandDispatcher<FabricClientCommandSource>
RelogCommand.register(dispatcher);
RenderCommand.register(dispatcher);
ReplyCommand.register(dispatcher);
ServerSeedCommand.register(dispatcher);
ShrugCommand.register(dispatcher);
SignSearchCommand.register(dispatcher);
SnakeCommand.register(dispatcher);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.earthcomputer.clientcommands.command;

import com.google.common.hash.HashCode;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.seedfinding.mccore.rand.seed.WorldSeed;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.List;

import static com.mojang.brigadier.arguments.LongArgumentType.*;
import static net.fabricmc.fabric.api.client.command.v2.ClientCommands.*;

public class ServerSeedCommand {

private static final SimpleCommandExceptionType IMPOSSIBLE_SEED_COMBINATION_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cserverseed.hashedSeed.impossibleSeedCombination"));
private static final SimpleCommandExceptionType ENCHANTMENT_SEED_NOT_SENT_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.cserverseed.enchantmentSeedNotSent"));

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(literal("cserverseed")
.then(literal("enchantmentSeed")
.executes(ctx -> enchantmentSeed(ctx.getSource())))
.then(literal("hashedSeed")
.executes(ctx -> hashedSeed(ctx.getSource()))
.then(argument("structureSeed", longArg())
.executes(ctx -> fromStructureSeed(ctx.getSource(), getLong(ctx, "structureSeed"))))));
}

private static int enchantmentSeed(FabricClientCommandSource source) throws CommandSyntaxException {
int seed = source.getPlayer().getEnchantmentSeed();
if (seed == 0) {
throw ENCHANTMENT_SEED_NOT_SENT_EXCEPTION.create();
}
// The enchantment seed is a Java int, which is 32 bits.
// However, the server sends the enchantment seed as short to the client.
// This causes the 16 higher bits to be ignored, leaving only the 16 lower bits.
seed &= 0x0000ffff;
source.sendFeedback(Component.translatable("commands.cserverseed.enchantmentSeed", ComponentUtils.copyOnClickText(StringUtils.leftPad(Integer.toBinaryString(seed), 16, '0'))));
return seed;
}

private static int hashedSeed(FabricClientCommandSource source) {
long hashedSeed = source.getLevel().getBiomeManager().biomeZoomSeed;
HashCode seedHash = HashCode.fromLong(hashedSeed);
source.sendFeedback(Component.translatable("commands.cserverseed.hashedSeed", ComponentUtils.copyOnClickText(seedHash.toString()), ComponentUtils.copyOnClickText(String.valueOf(seedHash.asLong()))));
return (int) hashedSeed;
}

private static int fromStructureSeed(FabricClientCommandSource source, long structureSeed) throws CommandSyntaxException {
long hashedSeed = source.getLevel().getBiomeManager().biomeZoomSeed;
List<Long> seeds = WorldSeed.fromHash(structureSeed, hashedSeed);
if (seeds.isEmpty()) {
throw IMPOSSIBLE_SEED_COMBINATION_EXCEPTION.create();
}
// Collision is extremely unlikely, so just call `getFirst`
long worldSeed = seeds.getFirst();
source.sendFeedback(Component.translatable("commands.cserverseed.hashedSeed.fromStructureSeed", ComponentUtils.copyOnClickText(String.valueOf(worldSeed))));
return (int) worldSeed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.earthcomputer.clientcommands.mixin.commands.serverseed;

import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.EnchantmentMenu;
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;

@Mixin(ClientPacketListener.class)
public class ClientPacketListenerMixin {
@Inject(method = "handleContainerSetData", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/inventory/AbstractContainerMenu;setData(II)V", shift = At.Shift.AFTER))
private void onContainerData(ClientboundContainerSetDataPacket packet, CallbackInfo ci, @Local Player player) {
if (player.containerMenu instanceof EnchantmentMenu menu) {
player.enchantmentSeed = menu.getEnchantmentSeed();
}
}
}
6 changes: 6 additions & 0 deletions src/main/resources/assets/clientcommands/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@
"commands.creply.messageTooLong": "Your reply was too long (maximum: %s, given: %s)",
"commands.creply.noTargetFound": "Could not find a target to reply to",

"commands.cserverseed.enchantmentSeed": "Lower 16 bits of the enchantment seed: %s",
"commands.cserverseed.enchantmentSeedNotSent": "The server has not sent the partial enchantment seed yet!",
"commands.cserverseed.hashedSeed": "First 8 bytes of the hashed world seed: %s (decimal: %s)",
"commands.cserverseed.hashedSeed.fromStructureSeed": "Found world seed: %s",
"commands.cserverseed.hashedSeed.impossibleSeedCombination": "Impossible seed combination!",

"commands.csignsearch.starting": "Searching signs",

"commands.csnap.airborne": "You cannot snap while airborne",
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/clientcommands.aw
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ accessible method net/minecraft/client/renderer/GameRenderer setPostEffect (Lnet
accessible field net/minecraft/client/renderer/ShaderManager compilationCache Lnet/minecraft/client/renderer/ShaderManager$CompilationCache;
accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache configs Lnet/minecraft/client/renderer/ShaderManager$Configs;

# cserverseed
accessible field net/minecraft/world/entity/player/Player enchantmentSeed I
accessible field net/minecraft/world/level/biome/BiomeManager biomeZoomSeed J

# Game Options
accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object;

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/mixins.clientcommands.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"commands.generic.CommandSuggestionsMixin",
"commands.glow.LivingEntityRenderStateMixin",
"commands.reply.ClientPacketListenerMixin",
"commands.serverseed.ClientPacketListenerMixin",
"commands.snap.MinecraftMixin",
"commands.time.ClientClockManagerMixin",
"dataqueryhandler.ClientPacketListenerMixin",
Expand Down
Loading