Skip to content

Commit

Permalink
ok, now that it's been held on to, i can try to predict it, but for s…
Browse files Browse the repository at this point in the history
…ome reason it's not working, idk why
  • Loading branch information
RealRTTV committed May 31, 2024
1 parent b935782 commit fe4133a
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.earthcomputer.clientcommands.features.VillagerCracker;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
Expand All @@ -22,17 +21,17 @@ public class VillagerCommand {
public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(
literal("cvillager")
.then(literal("timer")
.then(argument("value", blockPos())
.executes(ctx -> setTimerBlockPos(ctx.getSource(), getBlockPos(ctx, "value")))))
.then(literal("clock")
.then(argument("pos", blockPos())
.executes(ctx -> setClockBlockPos(ctx.getSource(), getBlockPos(ctx, "pos")))))
.then(literal("target")
.then(argument("value", entity())
.executes(ctx -> setVillagerTarget(ctx.getSource(), getEntity(ctx, "value"))))));
.then(argument("entity", entity())
.executes(ctx -> setVillagerTarget(ctx.getSource(), getEntity(ctx, "entity"))))));
}

private static int setTimerBlockPos(FabricClientCommandSource source, BlockPos pos) {
VillagerCracker.timerBlockPos = pos;
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.timerSet", pos.getX(), pos.getY(), pos.getZ()));
private static int setClockBlockPos(FabricClientCommandSource source, BlockPos pos) {
VillagerCracker.clockBlockPos = pos;
source.getPlayer().sendSystemMessage(Component.translatable("commands.cvillager.clockSet", pos.getX(), pos.getY(), pos.getZ()));
return Command.SINGLE_SUCCESS;
}

Expand All @@ -42,7 +41,7 @@ private static int setVillagerTarget(FabricClientCommandSource source, Entity ta
}

VillagerCracker.setTargetVillager(villager);
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.targetSet"));
source.getPlayer().sendSystemMessage(Component.translatable("commands.cvillager.targetSet"));

return Command.SINGLE_SUCCESS;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class VillagerCracker {
private static WeakReference<Villager> cachedVillager = null;

@Nullable
public static BlockPos timerBlockPos = null;
public static BlockPos clockBlockPos = null;

@Nullable
public static Villager getVillager() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
package net.earthcomputer.clientcommands.features;

import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerData;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.List;

public class VillagerRngSimulator {
private static final Logger LOGGER = LogUtils.getLogger();

@Nullable
private final LegacyRandomSource random;
private LegacyRandomSource random;
private int ambientSoundTime;
private int waitingTicks = 0;
private boolean madeSound = false;
private boolean firstAmbientNoise = true;

public VillagerRngSimulator(@Nullable LegacyRandomSource random, int ambientSoundTime) {
this.random = random;
this.ambientSoundTime = ambientSoundTime;
}

@Override
protected Object clone() {
return new VillagerRngSimulator(random == null ? null : new LegacyRandomSource(random.seed.get() ^ 0x5deece66dL), ambientSoundTime);
public VillagerRngSimulator clone() {
VillagerRngSimulator that = new VillagerRngSimulator(random == null ? null : new LegacyRandomSource(random.seed.get() ^ 0x5deece66dL), ambientSoundTime);
that.waitingTicks = this.waitingTicks;
that.madeSound = this.madeSound;
that.firstAmbientNoise = this.firstAmbientNoise;
return that;
}

public boolean simulateTick() {
boolean madeSound;
public void simulateTick() {
if (waitingTicks > 0) {
waitingTicks--;
return;
}

LOGGER.info("Client (pre-tick): {}", this);

if (random == null) {
return false;
return;
}

if (random.nextInt(1000) < ambientSoundTime++) {
// we have the server receiving ambient noise tell us if we have to do this to increment the random, this is so that our ambient sound time is synced up.
if (random.nextInt(1000) < ambientSoundTime++ && !firstAmbientNoise) {
random.nextFloat();
random.nextFloat();
ambientSoundTime = -80;
Expand All @@ -34,15 +63,47 @@ public boolean simulateTick() {
madeSound = false;
}

return madeSound;
random.nextInt(100);
}

@Nullable
public MerchantOffers simulateTrades(Villager villager) {
VillagerData villagerData = villager.getVillagerData();
Int2ObjectMap<VillagerTrades.ItemListing[]> map = VillagerTrades.TRADES.get(villagerData.getProfession());

if (map == null || map.isEmpty()) {
return null;
}

return simulateOffers(map.get(villagerData.getLevel()), villager);
}

public LegacyRandomSource random() {
private MerchantOffers simulateOffers(VillagerTrades.ItemListing[] listings, Entity trader) {
if (random == null) {
return null;
}

MerchantOffers offers = new MerchantOffers();
ArrayList<VillagerTrades.ItemListing> newListings = new ArrayList<>(List.of(listings));
int i = 0;
while (i < 2 && !newListings.isEmpty()) {
VillagerTrades.ItemListing listing = newListings.remove(random.nextInt(newListings.size()));
MerchantOffer offer = listing.getOffer(trader, random);
if (offer != null) {
offers.add(offer);
i++;
}
}
return offers;
}

@Nullable
public LegacyRandomSource getRandom() {
return random;
}

public int getAmbientSoundTime() {
return ambientSoundTime;
public void setRandom(@Nullable LegacyRandomSource random) {
this.random = random;
}

@Override
Expand All @@ -53,10 +114,45 @@ public String toString() {
}

public void onAmbientSoundPlayed() {
ambientSoundTime = -80;
if (random != null) {
if (firstAmbientNoise) {
if (random == null) {
return;
}

firstAmbientNoise = false;
ambientSoundTime = -80;
random.nextFloat();
random.nextFloat();
madeSound = true;
}

// is in sync
if (madeSound) {
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.perfectlyInSync"));
return;
}

// is not in sync, needs to be re-synced
VillagerRngSimulator clone = clone();
int i = 0;
while (!clone.madeSound) {
i++;
clone.simulateTick();
}
// todo, use ping if it's meant to be used (the idea here is to sync up to when the server says the villager makes a noise)
if (0 < i && i < 30) {
// in this case, it's a believable jump that we're less than 30 ticks behind, so we'll advance by the amount we calculated to be what this tick should've been
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.tooManyTicksBehind", i));
this.random = clone.random;
this.ambientSoundTime = clone.ambientSoundTime;
this.waitingTicks = clone.waitingTicks;
this.madeSound = clone.madeSound;
} else if (i > 30) {
// in this case, it took so many ticks to advance to rsync, that it's safe to assume we are ahead of the server, so we'll let the server catch up by 30 ticks
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.tooManyTicksAhead"));
waitingTicks += 30;
} else {
Minecraft.getInstance().player.sendSystemMessage(Component.translatable("commands.cvillager.perfectlyInSync"));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,47 @@
package net.earthcomputer.clientcommands.mixin.commands.villager;

import com.mojang.logging.LogUtils;
import net.earthcomputer.clientcommands.features.VillagerCracker;
import net.earthcomputer.clientcommands.features.VillagerRngSimulator;
import net.earthcomputer.clientcommands.interfaces.IVillager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.npc.AbstractVillager;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.trading.MerchantOffer;
import net.minecraft.world.item.trading.MerchantOffers;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import org.slf4j.Logger;
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.CallbackInfo;

import java.util.stream.Collectors;

@Mixin(Villager.class)
public class VillagerMixin implements IVillager {
public abstract class VillagerMixin extends AbstractVillager implements IVillager {
@Unique
VillagerRngSimulator rng = new VillagerRngSimulator(null, 0);
private static final Logger LOGGER = LogUtils.getLogger();

public VillagerMixin(EntityType<? extends AbstractVillager> entityType, Level level) {
super(entityType, level);
}

@Unique
boolean hasSetCrackedAmbientSoundTime = false;
VillagerRngSimulator rng = new VillagerRngSimulator(null, -80);

@Override
public void clientcommands_setCrackedRandom(RandomSource random) {
rng = new VillagerRngSimulator((LegacyRandomSource) random, rng.getAmbientSoundTime());
hasSetCrackedAmbientSoundTime = false;
rng.setRandom((LegacyRandomSource) random);
}

@Override
Expand All @@ -31,16 +51,59 @@ public VillagerRngSimulator clientcommands_getCrackedRandom() {

@Override
public void clientcommands_onAmbientSoundPlayed() {
if (!hasSetCrackedAmbientSoundTime) {
rng.onAmbientSoundPlayed();
}
rng.onAmbientSoundPlayed();
}

@Override
public void clientcommands_onServerTick() {
if (rng.simulateTick()) {
hasSetCrackedAmbientSoundTime = true;
Minecraft.getInstance().player.sendSystemMessage(Component.literal("hrmm"));
rng.simulateTick();
}

@Inject(method = "updateTrades", at = @At("HEAD"))
public void onUpdateTrades(CallbackInfo ci) {
if (!level().isClientSide) {
LOGGER.info("Server Seed (b4 trade): {}", ((LegacyRandomSource) random).seed.get());

Villager targetVillager = VillagerCracker.getVillager();
if (targetVillager != null && this.getUUID().equals(targetVillager.getUUID()) && ((IVillager) targetVillager).clientcommands_getCrackedRandom() != null) {
VillagerRngSimulator randomBranch = ((IVillager) targetVillager).clientcommands_getCrackedRandom().clone();
LOGGER.info("Client Seed (pre-interact): {}", randomBranch);
randomBranch.simulateTick();
LOGGER.info("Client Seed (post-tick): {}", randomBranch);
targetVillager.setVillagerData(targetVillager.getVillagerData().setProfession(VillagerProfession.LIBRARIAN));
MerchantOffers offers = randomBranch.simulateTrades(targetVillager);
targetVillager.setVillagerData(targetVillager.getVillagerData().setProfession(VillagerProfession.NONE));
if (offers == null) {
return;
}
for (MerchantOffer offer : offers) {
if (offer.getItemCostB().isPresent()) {
LOGGER.info("[x{}] {} + [x{}] {} = [x{}] {} ({})",
offer.getItemCostA().count(),
Component.translatable(BuiltInRegistries.ITEM.getKey(offer.getItemCostA().item().value()).getPath()).getString(),
offer.getItemCostB().get().count(),
Component.translatable(BuiltInRegistries.ITEM.getKey(offer.getItemCostB().get().item().value()).getPath()).getString(),
offer.getResult().getCount(),
I18n.get(BuiltInRegistries.ITEM.getKey(offer.getResult().getItem()).getPath()),
offer.getResult().getTooltipLines(Item.TooltipContext.EMPTY, null, TooltipFlag.NORMAL).stream().map(Component::getString).skip(1).collect(Collectors.joining(", ")));
} else {
LOGGER.info("[x{}] {} = [x{}] {} ({})",
offer.getItemCostA().count(),
Component.translatable(BuiltInRegistries.ITEM.getKey(offer.getItemCostA().item().value()).getPath()).getString(),
offer.getResult().getCount(),
Component.translatable(BuiltInRegistries.ITEM.getKey(offer.getResult().getItem()).getPath()).getString(),
offer.getResult().getTooltipLines(Item.TooltipContext.EMPTY, null, TooltipFlag.NORMAL).stream().map(Component::getString).skip(1).collect(Collectors.joining(", ")));
}
}
LOGGER.info("Client Seed (post-interact): {}", randomBranch);
}
}
}

@Inject(method = "tick", at = @At("HEAD"))
private void startTick(CallbackInfo ci) {
if (!level().isClientSide) {
LOGGER.info("Server Seed (pre-tick): {}", ((LegacyRandomSource) random).seed.get());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ private void onHandleSoundEvent(ClientboundSoundPacket packet, CallbackInfo ci)

@Inject(method = "handleBlockUpdate", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/util/thread/BlockableEventLoop;)V"))
private void onHandleBlockUpdate(ClientboundBlockUpdatePacket packet, CallbackInfo ci) {
if (packet.getPos().equals(VillagerCracker.timerBlockPos)) {
if (packet.getPos().equals(VillagerCracker.clockBlockPos)) {
VillagerCracker.onServerTick();
}
}

@Inject(method = "handleChunkBlocksUpdate", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/network/protocol/PacketUtils;ensureRunningOnSameThread(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketListener;Lnet/minecraft/util/thread/BlockableEventLoop;)V"))
private void onHandleChunkBlocksUpdate(ClientboundSectionBlocksUpdatePacket packet, CallbackInfo ci) {
packet.runUpdates((pos, state) -> {
if (pos.equals(VillagerCracker.timerBlockPos)) {
if (pos.equals(VillagerCracker.clockBlockPos)) {
VillagerCracker.onServerTick();
}
});
Expand Down
7 changes: 5 additions & 2 deletions src/main/resources/assets/clientcommands/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@
"commands.ccrackrng.success": "Player RNG cracked: %d",

"commands.cvillager.notAVillager": "Target was not a villager",
"commands.cvillager.targetSet": "Target set",
"commands.cvillager.timerSet": "Timer block position set to %d %d %d",
"commands.cvillager.targetSet": "Target entity set",
"commands.cvillager.clockSet": "Clock set to %d %d %d",
"commands.cvillager.crackFailed": "Failed to crack villager seed",
"commands.cvillager.crackSuccess": "Successfully cracked villager seed: %d",
"commands.cvillager.tooManyTicksAhead": "Was too many ticks ahead of villager cracking, awaiting the next 30 ticks of processing. Will likely need another ambient sound to resync",
"commands.cvillager.tooManyTicksBehind": "Was too many ticks behind of villager cracking, simulating by %d ticks, should be in sync.",
"commands.cvillager.perfectlyInSync": "Your villager's random is perfectly in sync",

"commands.ccreativetab.addStack.success": "Successfully added %s to \"%s\"",
"commands.ccreativetab.addTab.alreadyExists": "Creative tab \"%s\" already exists",
Expand Down

0 comments on commit fe4133a

Please sign in to comment.