Skip to content

Instantly share code, notes, and snippets.

@mjtb49
Last active September 21, 2023 11:20
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mjtb49/f3e01e3355178d2bb6c814606971c374 to your computer and use it in GitHub Desktop.
Save mjtb49/f3e01e3355178d2bb6c814606971c374 to your computer and use it in GitHub Desktop.
1.20-pre4 Loot Example
import com.google.common.base.Charsets;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;
import java.util.Random;
public class LootTableRNG {
private static final HashFunction MD5 = Hashing.md5();
private long seedLo;
private long seedHi;
private final long seedLoHash;
private final long seedHiHash;
public LootTableRNG(String identifier) {
byte[] bs = MD5.hashString(identifier, Charsets.UTF_8).asBytes();
seedLoHash = Longs.fromBytes(bs[0], bs[1], bs[2], bs[3], bs[4], bs[5], bs[6], bs[7]);
seedHiHash = Longs.fromBytes(bs[8], bs[9], bs[10], bs[11], bs[12], bs[13], bs[14], bs[15]);
}
public void setSeed(long seed) {
long l_seed = seed ^ 0x6A09E667F3BCC909L;
this.seedLo = mixStafford13(l_seed) ^ seedLoHash;
this.seedHi = mixStafford13(l_seed + -7046029254386353131L) ^ seedHiHash;
if ((this.seedLo | this.seedHi) == 0L) {
this.seedLo = -7046029254386353131L;
this.seedHi = 7640891576956012809L;
}
//1.20-pre3 burns a nextLong() call
this.nextLong();
}
public static long mixStafford13(long seed) {
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
return seed ^ seed >>> 31;
}
public long nextLong() {
long l = this.seedLo;
long m = this.seedHi;
//return value only
long n = Long.rotateLeft(l + m, 17) + l;
//actual generator
m ^= l;
this.seedLo = Long.rotateLeft(l, 49) ^ m ^ m << 21;
this.seedHi = Long.rotateLeft(m, 28);
return n;
}
private long nextBits(int i) {
return this.nextLong() >>> 64 - i;
}
public float nextFloat() {
return (float)this.nextBits(24) * 5.9604645E-8F;
}
public double nextDouble() {
return (double)this.nextBits(53) * 1.110223E-16F;
}
public int nextInt() {
return (int)this.nextLong();
}
public int nextInt(int i) {
if (i <= 0) {
throw new IllegalArgumentException("Bound must be positive");
} else {
long l = Integer.toUnsignedLong(this.nextInt());
long m = l * (long)i;
long n = m & 4294967295L;
if (n < (long)i) {
for(int j = Integer.remainderUnsigned(~i + 1, i); n < (long)j; n = m & 4294967295L) {
l = Integer.toUnsignedLong(this.nextInt());
m = l * (long)i;
}
}
long o = m >> 32;
return (int)o;
}
}
public static String toPercentage(float n, int digits){
return String.format("%."+digits+"f",n*100);
}
public static boolean test_loot(LootTableRNG rng) {
//Rewrite this for each loot table. Be careful since loot tables might do extra calls besides what you target,
//which this method must do as well. That is, this method should imitate a full call to the loot table.
//You must look in the code to determine exactly the nature of the needed test, or experiment
//determines melon slice count
int val = rng.nextInt(5);
//melons have something extra in their loot table which does a call after the number of slices is chosen
//the next line accounts for that
rng.nextLong();
return (val == 4);
}
public static void main(String[] args) {
int max = 0;
//Loot tables can be found by renaming the 1.20-pre2 jar to a .zip
//then navigating to /data/minecraft/loot_tables
//The needed string is at the bottom of a loot table
//You also must modify the test_loot function
LootTableRNG target = new LootTableRNG("minecraft:blocks/melon");
for (long seed = 0;; seed++) {
target.setSeed(seed);
int count = 0;
while (test_loot(target)) {
count += 1;
}
if (count > max) {
max = count;
System.out.println("Found " + count);
System.out.println(seed);
}
}
//System.out.println("Done!");
/*
//Code for correlation finding. Be very careful that the RNGs you target don't do extra calls - if they do
//correlations will still exist but you need to account for the calls when comparing.
int calls = 1000;
LootTableRNG rng1 = new LootTableRNG("minecraft:blocks/melon");
int rng1_calls = 5;
LootTableRNG rng2 = new LootTableRNG("minecraft:entities/blaze");
int rng2_calls = 2;
int[][][] heatmap = new int[calls][rng1_calls][rng2_calls];
for(long attempt = 0; attempt < 100000; attempt++) {
long seed = new Random().nextLong();
rng1.setSeed(seed);
rng2.setSeed(seed);
for (int call = 0; call < calls; call ++) {
heatmap[call][rng1.nextInt(rng1_calls)][rng2.nextInt(rng2_calls)] += 1;
//Burn a call because melons do (and blazes don't, so we need to kill blazes at twice the rate as the melons).
rng1.nextLong();
rng2.nextLong();
}
}
for (int call = 0; call < calls; call ++) {
boolean should_print = false;
for(int[] row: heatmap[call]) {
int sum = 0;
for (int i : row) {
sum += i;
}
for (int i : row) {
should_print |= Math.abs(i/(float) sum - 0.5) > 0.49;
}
}
if (should_print) {
System.out.println("Blaze " + (2 * call + 1) + " Melon " + (call + 1));
for (int[] row : heatmap[call]) {
int sum = 0;
for (int i : row) {
sum += i;
}
for (int i : row) {
System.out.print(toPercentage(i / (float) sum, 2) + " ");
}
System.out.println();
}
System.out.println();
}
}*/
}
}
@jakiki6
Copy link

jakiki6 commented May 23, 2023

btw the burn call in setSeed was removed in 1.20pre5:

     private static XoroshiroRandomSource createSequence(long l, ResourceLocation resourceLocation) {
-        XoroshiroRandomSource xoroshiroRandomSource = new XoroshiroRandomSource(RandomSupport.upgradeSeedTo128bit(l).xor(RandomSequence.seedForKey(resourceLocation)));
-        xoroshiroRandomSource.nextLong();
-        return xoroshiroRandomSource;
+        return new XoroshiroRandomSource(RandomSupport.upgradeSeedTo128bitUnmixed(l).xor(RandomSequence.seedForKey(resourceLocation)).mixed());
     }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment