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..8291a0f --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/mixin/server/subserver/WorldMixin.java @@ -0,0 +1,30 @@ +package io.ib67.sfcraft.mixin.server.subserver; + +import io.ib67.sfcraft.SFCraft; +import io.ib67.sfcraft.module.RoomModule; +import net.minecraft.registry.RegistryKey; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.GameRules; +import net.minecraft.world.World; +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.CallbackInfoReturnable; + +@Mixin(World.class) +public abstract class WorldMixin { + @Shadow public abstract boolean isClient(); + @Shadow @Final private RegistryKey registryKey; + + @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); + } + } +} diff --git a/src/main/java/io/ib67/sfcraft/module/RoomModule.java b/src/main/java/io/ib67/sfcraft/module/RoomModule.java index d350a00..92be63a 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; @@ -24,12 +25,14 @@ 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; 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 +62,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() { @@ -71,7 +75,7 @@ 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); @@ -96,18 +100,18 @@ 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 - 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); @@ -153,9 +157,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) { @@ -176,4 +178,8 @@ public class RoomModule extends ServerModule { public GameProfile devirtualize(UUID virtual) { return uuidMapper.get(virtual); } + + public GameRules readGameRuleForRoom(ServerWorld world){ + return RoomWorldDataLoader.get(world).getGameRules(); + } } 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 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..a9c5850 --- /dev/null +++ b/src/main/java/io/ib67/sfcraft/room/data/RoomWorldDataLoader.java @@ -0,0 +1,55 @@ +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.world.GameRules; +import net.minecraft.world.PersistentState; + +import java.util.Objects; + +@Getter +public class RoomWorldDataLoader extends PersistentState { + + 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"; + + 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, NAME); + 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(); + } +} 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": {