From f3fa16f6fcb2df104df9b032f9cfc2017406007b Mon Sep 17 00:00:00 2001 From: iceBear67 Date: Wed, 18 Sep 2024 10:52:22 +0800 Subject: [PATCH 1/4] add: per-world gamerules support for room. --- .../subserver/GameRuleCommandMixin.java | 29 ++++++++++ .../mixin/server/subserver/WorldMixin.java | 57 +++++++++++++++++++ .../io/ib67/sfcraft/module/RoomModule.java | 15 ++++- src/main/resources/sfcraft.mixins.json | 2 + 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/ib67/sfcraft/mixin/server/subserver/GameRuleCommandMixin.java create mode 100644 src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java diff --git a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/GameRuleCommandMixin.java b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/GameRuleCommandMixin.java new file mode 100644 index 0000000..b41c5f9 --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/GameRuleCommandMixin.java @@ -0,0 +1,29 @@ +package io.ib67.sfcraft.mixin.server.subserver; + +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.GameRuleCommand; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.world.GameRules; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(GameRuleCommand.class) +public abstract class GameRuleCommandMixin { + @Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getGameRules()Lnet/minecraft/world/GameRules;"), method = "executeSet") + private static GameRules sf$redirectSetForPerWorld(MinecraftServer instance, CommandContext context) { + return getGameRule(instance, context.getSource()); + } + + @Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getGameRules()Lnet/minecraft/world/GameRules;"), method = "executeQuery") + private static GameRules sf$redirectSetForPerWorld(MinecraftServer instance, ServerCommandSource context) { + return getGameRule(instance, context); + } + + private static GameRules getGameRule(MinecraftServer instance, ServerCommandSource source) { + var world = source.getWorld(); + if (world == null) return instance.getGameRules(); + return world.getGameRules(); // this is same as Server#getGameRule since one GameRules is shared across multiple worlds in vanilla. + } +} diff --git a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java new file mode 100644 index 0000000..cc333e6 --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java @@ -0,0 +1,57 @@ +package io.ib67.sfcraft.mixin.server.subserver; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import io.ib67.sfcraft.SFCraft; +import io.ib67.sfcraft.module.RoomModule; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.util.profiler.Profiler; +import net.minecraft.world.GameRules; +import net.minecraft.world.MutableWorldProperties; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.level.LevelProperties; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.function.Supplier; + +@Mixin(World.class) +public abstract class WorldMixin { + @Unique + private GameRules customGameRule; + + @Inject(at = @At("TAIL"), method = "") + private void sf$initCustomGameRule(MutableWorldProperties properties, + RegistryKey registryRef, + DynamicRegistryManager registryManager, + RegistryEntry dimensionEntry, + Supplier profiler, + boolean isClient, + boolean debugWorld, + long biomeAccess, + int maxChainedNeighborUpdates, CallbackInfo ci) { + var id = registryRef.getValue(); + if (id.getNamespace().equals("sfcraft")) { // room + customGameRule = SFCraft.getInjector().getInstance(RoomModule.class).readGameRuleForRoom(id); + } else { + customGameRule = properties.getGameRules(); + } + } + + /** + * @reason for per-world rules + * @author icybear + */ + @Overwrite + public GameRules getGameRules() { + return customGameRule; + } +} diff --git a/src/main/java/io/ib67/sfcraft/module/RoomModule.java b/src/main/java/io/ib67/sfcraft/module/RoomModule.java index d350a00..73e8189 100644 --- a/src/main/java/io/ib67/sfcraft/module/RoomModule.java +++ b/src/main/java/io/ib67/sfcraft/module/RoomModule.java @@ -30,6 +30,7 @@ import net.minecraft.util.Pair; import net.minecraft.util.Unit; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.GameRules; import net.minecraft.world.World; import org.apache.commons.lang3.RandomStringUtils; @@ -59,6 +60,7 @@ public class RoomModule extends ServerModule { private Key key; private final List, BlockPos>> pregenQueue = new ArrayList<>(); private final Map uuidMapper = new ConcurrentHashMap<>(); + private final Map gameRulesPerRoom = new HashMap<>(); @Override public void onInitialize() { @@ -78,6 +80,11 @@ public class RoomModule extends ServerModule { } } + @Override + public void onDisable() { + //todo persistence of gameRulesPerRoom + } + private void registerCommand(CommandDispatcher serverCommandSourceCommandDispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { serverCommandSourceCommandDispatcher.register( LiteralArgumentBuilder.literal("reco") @@ -100,14 +107,14 @@ public class RoomModule extends ServerModule { } @SneakyThrows - public byte[] encrypt(byte[] b) { + byte[] encrypt(byte[] b) { var cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, key); return cipher.doFinal(b); } @SneakyThrows - public byte[] decrypt(byte[] b) { + byte[] decrypt(byte[] b) { var cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(b); @@ -176,4 +183,8 @@ public class RoomModule extends ServerModule { public GameProfile devirtualize(UUID virtual) { return uuidMapper.get(virtual); } + + public GameRules readGameRuleForRoom(Identifier identifier){ + return gameRulesPerRoom.computeIfAbsent(identifier, k -> new GameRules()); + } } diff --git a/src/main/resources/sfcraft.mixins.json b/src/main/resources/sfcraft.mixins.json index 4258779..e5c2636 100644 --- a/src/main/resources/sfcraft.mixins.json +++ b/src/main/resources/sfcraft.mixins.json @@ -34,6 +34,8 @@ "server.subserver.PlayerSaveHandlerMixin", "server.subserver.ServerPlayerEntityMixin", "server.subserver.PlayerListS2CPacket$EntryMixin", + "server.subserver.WorldMixin", + "server.subserver.GameRuleCommandMixin", "server.optimize.LivingEntityMixin" ], "injectors": { From a7e564d709053002c5674971ac5aa8015691487f Mon Sep 17 00:00:00 2001 From: iceBear67 Date: Wed, 18 Sep 2024 10:54:05 +0800 Subject: [PATCH 2/4] add: allow /gamerule in playground. --- .../java/io/ib67/sfcraft/module/room/CreativeRoomModule.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/ib67/sfcraft/module/room/CreativeRoomModule.java b/src/main/java/io/ib67/sfcraft/module/room/CreativeRoomModule.java index 05cc61c..f557e65 100644 --- a/src/main/java/io/ib67/sfcraft/module/room/CreativeRoomModule.java +++ b/src/main/java/io/ib67/sfcraft/module/room/CreativeRoomModule.java @@ -38,7 +38,8 @@ import java.util.Set; public class CreativeRoomModule extends ServerModule { private static final Set ALLOWED_VANILLA_COMMANDS = Set.of( "minecraft.command.weather", - "minecraft.command.time"); + "minecraft.command.time", + "minecraft.command.gamerule"); @Inject private RoomRegistry roomRegistry; @Inject From 40d3e8cf54cce868919354da8d6d46fb891394f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E9=9B=A8=E8=90=BD?= Date: Wed, 18 Sep 2024 14:45:16 +0800 Subject: [PATCH 3/4] add: Persistent gamerule per world. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 秋雨落 --- .../mixin/server/subserver/WorldMixin.java | 6 +- .../io/ib67/sfcraft/module/RoomModule.java | 18 ++---- .../room/data/RoomWorldDataLoader.java | 56 +++++++++++++++++++ 3 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java diff --git a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java index cc333e6..ee735c8 100644 --- a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java +++ b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java @@ -1,7 +1,5 @@ package io.ib67.sfcraft.mixin.server.subserver; -import com.llamalad7.mixinextras.sugar.Local; -import com.llamalad7.mixinextras.sugar.ref.LocalRef; import io.ib67.sfcraft.SFCraft; import io.ib67.sfcraft.module.RoomModule; import net.minecraft.registry.DynamicRegistryManager; @@ -12,13 +10,11 @@ import net.minecraft.world.GameRules; import net.minecraft.world.MutableWorldProperties; import net.minecraft.world.World; import net.minecraft.world.dimension.DimensionType; -import net.minecraft.world.level.LevelProperties; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.function.Supplier; @@ -40,7 +36,7 @@ public abstract class WorldMixin { int maxChainedNeighborUpdates, CallbackInfo ci) { var id = registryRef.getValue(); if (id.getNamespace().equals("sfcraft")) { // room - customGameRule = SFCraft.getInjector().getInstance(RoomModule.class).readGameRuleForRoom(id); + customGameRule = SFCraft.getInjector().getInstance(RoomModule.class).readGameRuleForRoom(registryRef); } else { customGameRule = properties.getGameRules(); } diff --git a/src/main/java/io/ib67/sfcraft/module/RoomModule.java b/src/main/java/io/ib67/sfcraft/module/RoomModule.java index 73e8189..ef7ca42 100644 --- a/src/main/java/io/ib67/sfcraft/module/RoomModule.java +++ b/src/main/java/io/ib67/sfcraft/module/RoomModule.java @@ -10,6 +10,7 @@ import io.ib67.sfcraft.config.SFConfig; import io.ib67.sfcraft.inject.MinecraftServerSupplier; import io.ib67.sfcraft.room.RequestedRoom; import io.ib67.sfcraft.registry.RoomRegistry; +import io.ib67.sfcraft.room.data.RoomWorldDataLoader; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.SneakyThrows; @@ -73,18 +74,13 @@ public class RoomModule extends ServerModule { for (Pair, BlockPos> registryKeyBlockPosPair : pregenQueue) { var world = serverSupplier.get().getWorld(registryKeyBlockPosPair.getLeft()); if (world == null) { - log.warn("Cannot find world " + registryKeyBlockPosPair.getLeft()); + log.warn("Cannot find world {}", registryKeyBlockPosPair.getLeft()); continue; } world.getChunkManager().addTicket(ChunkTicketType.START, new ChunkPos(registryKeyBlockPosPair.getRight()), 64, Unit.INSTANCE); } } - @Override - public void onDisable() { - //todo persistence of gameRulesPerRoom - } - private void registerCommand(CommandDispatcher serverCommandSourceCommandDispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) { serverCommandSourceCommandDispatcher.register( LiteralArgumentBuilder.literal("reco") @@ -103,7 +99,7 @@ public class RoomModule extends ServerModule { networkHandler.send(new StoreCookieS2CPacket(ROOM_COOKIE, EMPTY), PacketCallbacks.always(() -> { networkHandler.sendPacket(new ServerTransferS2CPacket(config.domain, serverSupplier.get().getServerPort())); })); - return 0; + return 1; } @SneakyThrows @@ -160,9 +156,7 @@ public class RoomModule extends ServerModule { private static SecretKey getKeyFromPassword(String password, String salt) { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256); - SecretKey secret = new SecretKeySpec(factory.generateSecret(spec) - .getEncoded(), "AES"); - return secret; + return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); } public void enqueuePregen(RegistryKey world, BlockPos spawnPos) { @@ -184,7 +178,7 @@ public class RoomModule extends ServerModule { return uuidMapper.get(virtual); } - public GameRules readGameRuleForRoom(Identifier identifier){ - return gameRulesPerRoom.computeIfAbsent(identifier, k -> new GameRules()); + public GameRules readGameRuleForRoom(RegistryKey key){ + return RoomWorldDataLoader.get(serverSupplier.get().getWorld(key)).getGameRules(); } } diff --git a/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java b/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java new file mode 100644 index 0000000..f99fae9 --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java @@ -0,0 +1,56 @@ +package io.ib67.sfcraft.room.data; + +import com.mojang.serialization.Dynamic; +import lombok.Getter; +import net.minecraft.datafixer.DataFixTypes; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtOps; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.world.GameRules; +import net.minecraft.world.PersistentState; + +import java.util.Objects; + +@Getter +public class RoomWorldDataLoader extends PersistentState { + + private static final Identifier ID = Identifier.of("sfcraft", "room_data"); + private static final Type TYPE = new Type<>(RoomWorldDataLoader::new, RoomWorldDataLoader::readNbt, DataFixTypes.LEVEL); + + private static final String TAG_GAME_RULES = "gameRules"; + + private final GameRules gameRules; + + public RoomWorldDataLoader() { + this(new GameRules()); + } + + public RoomWorldDataLoader(GameRules gameRules) { + this.gameRules = gameRules; + } + + public static RoomWorldDataLoader get(ServerWorld world) { + Objects.requireNonNull(world); + var state = world.getPersistentStateManager().getOrCreate(TYPE, ID.toString()); + state.markDirty(); + return state; + } + + @Override + public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registry) { + nbt.put(TAG_GAME_RULES, gameRules.toNbt()); + return nbt; + } + + private static RoomWorldDataLoader readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registry) { + if (nbt.contains(TAG_GAME_RULES)) { + var c = nbt.getCompound(TAG_GAME_RULES); + var dynamic = new Dynamic<>(NbtOps.INSTANCE, c); + var rule = new GameRules(dynamic); + return new RoomWorldDataLoader(rule); + } + return new RoomWorldDataLoader(); + } +} From 6022e1ba9a9763916430b3eb7ae43851409e06b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E9=9B=A8=E8=90=BD?= Date: Wed, 18 Sep 2024 15:00:39 +0800 Subject: [PATCH 4/4] fix: runtime error. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 秋雨落 --- .../mixin/server/subserver/WorldMixin.java | 51 +++++-------------- .../io/ib67/sfcraft/module/RoomModule.java | 5 +- .../room/data/RoomWorldDataLoader.java | 5 +- 3 files changed, 19 insertions(+), 42 deletions(-) diff --git a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java index ee735c8..8291a0f 100644 --- a/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java +++ b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java @@ -2,52 +2,29 @@ package io.ib67.sfcraft.mixin.server.subserver; import io.ib67.sfcraft.SFCraft; import io.ib67.sfcraft.module.RoomModule; -import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.registry.RegistryKey; -import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.util.profiler.Profiler; +import net.minecraft.server.world.ServerWorld; import net.minecraft.world.GameRules; -import net.minecraft.world.MutableWorldProperties; import net.minecraft.world.World; -import net.minecraft.world.dimension.DimensionType; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.*; 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.function.Supplier; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(World.class) public abstract class WorldMixin { - @Unique - private GameRules customGameRule; + @Shadow public abstract boolean isClient(); + @Shadow @Final private RegistryKey registryKey; - @Inject(at = @At("TAIL"), method = "") - private void sf$initCustomGameRule(MutableWorldProperties properties, - RegistryKey registryRef, - DynamicRegistryManager registryManager, - RegistryEntry dimensionEntry, - Supplier profiler, - boolean isClient, - boolean debugWorld, - long biomeAccess, - int maxChainedNeighborUpdates, CallbackInfo ci) { - var id = registryRef.getValue(); - if (id.getNamespace().equals("sfcraft")) { // room - customGameRule = SFCraft.getInjector().getInstance(RoomModule.class).readGameRuleForRoom(registryRef); - } else { - customGameRule = properties.getGameRules(); + @Unique private GameRules sfcraft$customGameRule; + + @Inject(method = "getGameRules", at = @At("HEAD"), cancellable = true) + private void beforeGetGameRules(CallbackInfoReturnable cir) { + if (!isClient() && registryKey.getValue().getNamespace().equals("sfcraft")) { + if (sfcraft$customGameRule == null) { + sfcraft$customGameRule = SFCraft.getInjector().getInstance(RoomModule.class).readGameRuleForRoom((ServerWorld) (Object) this); + } + cir.setReturnValue(sfcraft$customGameRule); } } - - /** - * @reason for per-world rules - * @author icybear - */ - @Overwrite - public GameRules getGameRules() { - return customGameRule; - } } diff --git a/src/main/java/io/ib67/sfcraft/module/RoomModule.java b/src/main/java/io/ib67/sfcraft/module/RoomModule.java index ef7ca42..92be63a 100644 --- a/src/main/java/io/ib67/sfcraft/module/RoomModule.java +++ b/src/main/java/io/ib67/sfcraft/module/RoomModule.java @@ -25,6 +25,7 @@ import net.minecraft.registry.RegistryKey; import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.world.ChunkTicketType; +import net.minecraft.server.world.ServerWorld; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.Pair; @@ -178,7 +179,7 @@ public class RoomModule extends ServerModule { return uuidMapper.get(virtual); } - public GameRules readGameRuleForRoom(RegistryKey key){ - return RoomWorldDataLoader.get(serverSupplier.get().getWorld(key)).getGameRules(); + public GameRules readGameRuleForRoom(ServerWorld world){ + return RoomWorldDataLoader.get(world).getGameRules(); } } diff --git a/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java b/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java index f99fae9..a9c5850 100644 --- a/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java +++ b/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java @@ -7,7 +7,6 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtOps; import net.minecraft.registry.RegistryWrapper; import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.Identifier; import net.minecraft.world.GameRules; import net.minecraft.world.PersistentState; @@ -16,7 +15,7 @@ import java.util.Objects; @Getter public class RoomWorldDataLoader extends PersistentState { - private static final Identifier ID = Identifier.of("sfcraft", "room_data"); + private static final String NAME = "sfcraft_room_data"; private static final Type TYPE = new Type<>(RoomWorldDataLoader::new, RoomWorldDataLoader::readNbt, DataFixTypes.LEVEL); private static final String TAG_GAME_RULES = "gameRules"; @@ -33,7 +32,7 @@ public class RoomWorldDataLoader extends PersistentState { public static RoomWorldDataLoader get(ServerWorld world) { Objects.requireNonNull(world); - var state = world.getPersistentStateManager().getOrCreate(TYPE, ID.toString()); + var state = world.getPersistentStateManager().getOrCreate(TYPE, NAME); state.markDirty(); return state; }