Compare commits
3 Commits
72277eb71c
...
6e88427eef
Author | SHA1 | Date | |
---|---|---|---|
6e88427eef | |||
84e5ad9f2b | |||
8f509dcffb |
@ -133,6 +133,8 @@ dependencies {
|
||||
// For more info:
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
|
||||
// compileOnly(project(":c2me"))
|
||||
}
|
||||
|
||||
// This block of code expands all declared replace properties in the specified resource targets.
|
||||
|
@ -34,7 +34,7 @@ mod_name=Better Random
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=BSD 2 Clause
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=1.2.1
|
||||
mod_version=1.3.0
|
||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||
# This should match the base package used for the mod sources.
|
||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
|
@ -9,3 +9,5 @@ pluginManagement {
|
||||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
|
||||
}
|
||||
|
||||
// include("c2me")
|
||||
|
@ -1,9 +1,27 @@
|
||||
package ru.dbotthepony.mc.prng;
|
||||
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.neoforged.fml.ModList;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mod(BetterRandom.MOD_ID)
|
||||
public final class BetterRandom {
|
||||
public static final String MOD_ID = "better_random";
|
||||
public static final String SAVEDATA_LOCATION = "better_random_sequences";
|
||||
|
||||
private static RandomSource createWorldRandomC2ME(Supplier<Thread> thread) {
|
||||
// TODO: actually implement proper integration
|
||||
// also, this shouldn't be synchronized, per say, in this way, but instead throw an exception when
|
||||
// concurrently accessed (to match C2ME and vanilla behavior)
|
||||
return new SynchronizedGJRAND64RandomSource();
|
||||
}
|
||||
|
||||
public static RandomSource createWorldRandom(Supplier<Thread> thread) {
|
||||
if (ModList.get().isLoaded("c2me"))
|
||||
return createWorldRandomC2ME(thread);
|
||||
|
||||
return new GJRAND64RandomSource();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public final class GJRAND64RandomSource implements RandomGenerator, RandomSource {
|
||||
public class GJRAND64RandomSource implements RandomGenerator, RandomSource {
|
||||
public static final Codec<GJRAND64RandomSource> CODEC = RecordCodecBuilder.create(it -> {
|
||||
return it.group(
|
||||
Codec.LONG.fieldOf("s0").forGetter(o -> o.s0),
|
||||
|
@ -0,0 +1,95 @@
|
||||
package ru.dbotthepony.mc.prng;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class SynchronizedGJRAND64RandomSource extends GJRAND64RandomSource {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
public SynchronizedGJRAND64RandomSource() {
|
||||
super();
|
||||
}
|
||||
|
||||
public SynchronizedGJRAND64RandomSource(long seed) {
|
||||
super(seed);
|
||||
}
|
||||
|
||||
public SynchronizedGJRAND64RandomSource(long seed0, long seed1) {
|
||||
super(seed0, seed1);
|
||||
}
|
||||
|
||||
public SynchronizedGJRAND64RandomSource(RandomSupport.Seed128bit seed) {
|
||||
super(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
lock.lock();
|
||||
long result = super.nextLong();
|
||||
lock.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeed(long l) {
|
||||
lock.lock();
|
||||
super.setSeed(l);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextGaussian() {
|
||||
lock.lock();
|
||||
double value = super.nextGaussian();
|
||||
lock.unlock();
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RandomSource fork() {
|
||||
return new SynchronizedGJRAND64RandomSource(nextLong(), nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PositionalRandomFactory forkPositional() {
|
||||
return new Positional(nextLong(), nextLong());
|
||||
}
|
||||
|
||||
public static class Positional implements PositionalRandomFactory {
|
||||
private final long seed0;
|
||||
private final long seed1;
|
||||
|
||||
public Positional(long seed0, long seed1) {
|
||||
this.seed0 = seed0;
|
||||
this.seed1 = seed1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RandomSource fromHashOf(@NotNull String s) {
|
||||
long seed = HashCommon.murmurHash3((long) s.hashCode() & 0xFFFFFFFFL);
|
||||
return new SynchronizedGJRAND64RandomSource(seed0 ^ Long.rotateLeft(seed, 32), seed1 ^ seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RandomSource fromSeed(long l) {
|
||||
return new SynchronizedGJRAND64RandomSource(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull RandomSource at(int x, int y, int z) {
|
||||
long seed = Mth.getSeed(x, y, z);
|
||||
return new SynchronizedGJRAND64RandomSource(seed0 ^ Long.rotateLeft(seed, 32), seed1 ^ seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parityConfigString(@NotNull StringBuilder stringBuilder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,34 @@
|
||||
package ru.dbotthepony.mc.prng.mixin;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import ru.dbotthepony.mc.prng.BetterRandom;
|
||||
|
||||
@Mixin(Level.class)
|
||||
public abstract class LevelMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private Thread thread;
|
||||
|
||||
@Final // ensure it is on top-level
|
||||
@Redirect(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/util/RandomSource;create()Lnet/minecraft/util/RandomSource;"
|
||||
)
|
||||
)
|
||||
public RandomSource ensureBetterRandom() {
|
||||
return BetterRandom.createWorldRandom(() -> thread);
|
||||
}
|
||||
|
||||
@Overwrite
|
||||
public BlockPos getBlockRandomPos(int x, int y, int z, int yMask) {
|
||||
long value = ((Level) (Object) this).random.nextLong();
|
||||
|
@ -4,29 +4,39 @@ import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.world.level.levelgen.WorldgenRandom;
|
||||
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import ru.dbotthepony.mc.prng.GJRAND64RandomSource;
|
||||
|
||||
@Mixin(WorldgenRandom.class)
|
||||
public abstract class WorldgenRandomMixin {
|
||||
@Shadow
|
||||
protected RandomSource randomSource;
|
||||
private RandomSource randomSource;
|
||||
|
||||
// quite lazy solution because if someone genuinely want to get lcg they wont be able to
|
||||
@Inject(
|
||||
// This is a mixed bag os a solution.
|
||||
// On one hand, we cover all cases where WorldgenRandom getting called within vanilla code,
|
||||
// on other hand it also detours and redirects calls done by mods, which *may* delibarately use LCG or Xoroshiro128++
|
||||
// But we also need to consider that mods will likely just copy/do what vanilla code does, and hence
|
||||
// they expect code to follow same behavior as vanilla, so in such case us redirecting PRNG is completely desirable
|
||||
@Redirect(
|
||||
method = "<init>(Lnet/minecraft/util/RandomSource;)V",
|
||||
at = @At("TAIL")
|
||||
at = @At(
|
||||
value = "FIELD",
|
||||
target = "randomSource:Lnet/minecraft/util/RandomSource;",
|
||||
opcode = Opcodes.PUTFIELD
|
||||
)
|
||||
)
|
||||
public void constructorMix(RandomSource randomSource, CallbackInfo info) {
|
||||
public void constructorMix(WorldgenRandom self, RandomSource randomSource) {
|
||||
if (randomSource instanceof LegacyRandomSource rng) {
|
||||
this.randomSource = new GJRAND64RandomSource(rng.seed.get());
|
||||
} else if (randomSource instanceof XoroshiroRandomSource rng) {
|
||||
this.randomSource = new GJRAND64RandomSource(rng.randomNumberGenerator.seedHi, rng.randomNumberGenerator.seedLo);
|
||||
} else {
|
||||
this.randomSource = randomSource;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ protected net.minecraft.world.RandomSequences includeSequenceId
|
||||
public net.minecraft.world.RandomSequences$DirtyMarkingRandomSource
|
||||
public net.minecraft.world.RandomSequences$DirtyMarkingRandomSource <init>(Lnet/minecraft/world/RandomSequences;Lnet/minecraft/util/RandomSource;)V
|
||||
|
||||
protected-f net.minecraft.world.level.levelgen.WorldgenRandom randomSource
|
||||
private-f net.minecraft.world.level.levelgen.WorldgenRandom randomSource
|
||||
public net.minecraft.world.level.levelgen.LegacyRandomSource seed
|
||||
public net.minecraft.world.level.levelgen.XoroshiroRandomSource randomNumberGenerator
|
||||
public net.minecraft.world.level.levelgen.Xoroshiro128PlusPlus seedLo
|
||||
|
Loading…
Reference in New Issue
Block a user