Compare commits

...

3 Commits

Author SHA1 Message Date
6e88427eef
Bump version 2025-03-12 16:18:18 +07:00
84e5ad9f2b
Barebone c2me integration code 2025-03-12 16:12:31 +07:00
8f509dcffb
Use redirect in WorldgenRandom instead of overwriting 2025-03-12 15:45:48 +07:00
9 changed files with 159 additions and 10 deletions

View File

@ -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.

View File

@ -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

View File

@ -9,3 +9,5 @@ pluginManagement {
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
// include("c2me")

View File

@ -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();
}
}

View File

@ -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),

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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