diff --git a/common/src/main/java/me/shedaniel/architectury/event/events/ChunkEvent.java b/common/src/main/java/me/shedaniel/architectury/event/events/ChunkEvent.java new file mode 100644 index 00000000..b941d3af --- /dev/null +++ b/common/src/main/java/me/shedaniel/architectury/event/events/ChunkEvent.java @@ -0,0 +1,65 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.event.events; + +import me.shedaniel.architectury.event.Event; +import me.shedaniel.architectury.event.EventFactory; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ProtoChunk; +import org.jetbrains.annotations.Nullable; + +public interface ChunkEvent { + /** + * @see SaveData#save(ChunkAccess, ServerLevel, CompoundTag) + */ + Event SAVE_DATA = EventFactory.createLoop(); + /** + * @see LoadData#load(ChunkAccess, ServerLevel, CompoundTag) + */ + Event LOAD_DATA = EventFactory.createLoop(); + + interface SaveData { + /** + * Invoked when a chunk's data is saved, just before the data is written. + * Add your own data to the {@link CompoundTag} parameter to get your data saved as well. + * Equivalent to Forge's {@code ChunkDataEvent.Save}. + * + * @param chunk The chunk that is saved. + * @param level The level the chunk is in. + * @param nbt The chunk data that is written to the save file. + */ + void save(ChunkAccess chunk, ServerLevel level, CompoundTag nbt); + } + + interface LoadData { + /** + * Invoked just before a chunk's data is fully read. + * You can read out your own data from the {@link CompoundTag} parameter, when you have saved one before. + * Equivalent to Forge's {@code ChunkDataEvent.Load}. + * + * @param chunk The chunk that is loaded. + * @param level The level the chunk is in, may be {@code null}. + * @param nbt The chunk data that was read from the save file. + */ + void load(ChunkAccess chunk, @Nullable ServerLevel level, CompoundTag nbt); + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkMap.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkMap.java new file mode 100644 index 00000000..7db9065b --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkMap.java @@ -0,0 +1,51 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.ChunkEvent; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +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.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ChunkMap.class) +public class MixinChunkMap { + @Shadow + @Final + private ServerLevel level; + + @Inject( + method = "save", + at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)V", ordinal = 0), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void save(ChunkAccess chunkAccess, CallbackInfoReturnable cir, ChunkPos pos, ChunkStatus chunkStatus, CompoundTag nbt) { + ChunkEvent.SAVE_DATA.invoker().save(chunkAccess, this.level, nbt); + } +} diff --git a/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkSerializer.java b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkSerializer.java new file mode 100644 index 00000000..9e553b95 --- /dev/null +++ b/fabric/src/main/java/me/shedaniel/architectury/mixin/fabric/MixinChunkSerializer.java @@ -0,0 +1,49 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.fabric; + +import me.shedaniel.architectury.event.events.ChunkEvent; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.chunk.*; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; +import net.minecraft.world.level.lighting.LevelLightEngine; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(ChunkSerializer.class) +public class MixinChunkSerializer { + @Inject(method = "read", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private static void load(ServerLevel serverLevel, StructureManager structureManager, PoiManager poiManager, ChunkPos chunkPos, CompoundTag compoundTag, + CallbackInfoReturnable cir, ChunkGenerator chunkGenerator, BiomeSource biomeSource, CompoundTag compoundTagLevelData, + ChunkBiomeContainer chunkBiomeContainer, UpgradeData upgradeData, ProtoTickList protoTickList, ProtoTickList protoTickList2, + boolean bl, ListTag listTag, int i, LevelChunkSection[] levelChunkSections, boolean bl2, ChunkSource chunkSource, + LevelLightEngine levelLightEngine, long l, ChunkStatus.ChunkType chunkType, ChunkAccess chunk) { + ChunkEvent.LOAD_DATA.invoker().load(chunk, serverLevel, compoundTag); + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index 3859d93a..7bd53999 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -26,6 +26,8 @@ "MixinBlockItem", "MixinBucketItem", "MixinCatSpawner", + "MixinChunkMap", + "MixinChunkSerializer", "MixinCollisionContext", "MixinCommands", "MixinDedicatedServer", diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java index 057476b6..22773053 100644 --- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java +++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java @@ -31,6 +31,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; import net.minecraftforge.event.CommandEvent; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.ServerChatEvent; @@ -49,6 +50,7 @@ import net.minecraftforge.event.entity.player.PlayerEvent.*; import net.minecraftforge.event.world.BlockEvent.BreakEvent; import net.minecraftforge.event.world.BlockEvent.EntityPlaceEvent; import net.minecraftforge.event.world.BlockEvent.FarmlandTrampleEvent; +import net.minecraftforge.event.world.ChunkDataEvent; import net.minecraftforge.event.world.ExplosionEvent.Detonate; import net.minecraftforge.event.world.ExplosionEvent.Start; import net.minecraftforge.event.world.WorldEvent; @@ -371,6 +373,28 @@ public class EventHandlerImplCommon { } } + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(ChunkDataEvent.Save event) { + if (event.getWorld() instanceof ServerLevel) { + ChunkEvent.SAVE_DATA.invoker().save(event.getChunk(), (ServerLevel) event.getWorld(), event.getData()); + } + } + + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(ChunkDataEvent.Load event) { + LevelAccessor level = event.getChunk().getWorldForge(); + if (!(level instanceof ServerLevel)) { + level = ((WorldEventAttachment) event).architectury$getAttachedLevel(); + } + ChunkEvent.LOAD_DATA.invoker().load(event.getChunk(), level instanceof ServerLevel ? (ServerLevel) level : null, event.getData()); + } + + public interface WorldEventAttachment { + LevelAccessor architectury$getAttachedLevel(); + + void architectury$attachLevel(LevelAccessor level); + } + public static class ModBasedEventHandler { } diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinChunkSerializer.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinChunkSerializer.java new file mode 100644 index 00000000..48799adb --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinChunkSerializer.java @@ -0,0 +1,65 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.forge; + +import me.shedaniel.architectury.event.forge.EventHandlerImplCommon; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; +import net.minecraftforge.event.world.ChunkDataEvent; +import net.minecraftforge.eventbus.api.Event; +import org.spongepowered.asm.mixin.Mixin; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.lang.ref.WeakReference; + +@Mixin(ChunkSerializer.class) +public class MixinChunkSerializer { + @Unique + private static ThreadLocal> level = new ThreadLocal<>(); + + @Inject(method = "read", at = @At("HEAD")) + private static void read(ServerLevel worldIn, StructureManager templateManagerIn, PoiManager poiManager, ChunkPos pos, CompoundTag compound, CallbackInfoReturnable cir) { + level.set(new WeakReference<>(worldIn)); + } + + @ModifyArg(method = "read", at = @At(value = "INVOKE", + ordinal = 1, + target = "Lnet/minecraftforge/eventbus/api/IEventBus;post(Lnet/minecraftforge/eventbus/api/Event;)Z"), + index = 0) + private static Event modifyProtoChunkLevel(Event event) { + // We should get this PRed to Forge + WeakReference levelRef = level.get(); + if (levelRef != null && event instanceof ChunkDataEvent.Load) { + ChunkDataEvent.Load load = (ChunkDataEvent.Load) event; + ((EventHandlerImplCommon.WorldEventAttachment) load).architectury$attachLevel(levelRef.get()); + } + level.set(null); + return event; + } +} diff --git a/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinWorldEvent.java b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinWorldEvent.java new file mode 100644 index 00000000..d6cddfcf --- /dev/null +++ b/forge/src/main/java/me/shedaniel/architectury/mixin/forge/MixinWorldEvent.java @@ -0,0 +1,44 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package me.shedaniel.architectury.mixin.forge; + +import me.shedaniel.architectury.event.forge.EventHandlerImplCommon; +import net.minecraft.world.level.LevelAccessor; +import net.minecraftforge.event.world.WorldEvent; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +import java.lang.ref.WeakReference; + +@Mixin(WorldEvent.class) +public class MixinWorldEvent implements EventHandlerImplCommon.WorldEventAttachment { + @Unique + private WeakReference level; + + @Override + public LevelAccessor architectury$getAttachedLevel() { + return level == null ? null : level.get(); + } + + @Override + public void architectury$attachLevel(LevelAccessor level) { + this.level = new WeakReference<>(level); + } +} diff --git a/forge/src/main/resources/architectury.mixins.json b/forge/src/main/resources/architectury.mixins.json index e72ebe62..03998a95 100644 --- a/forge/src/main/resources/architectury.mixins.json +++ b/forge/src/main/resources/architectury.mixins.json @@ -15,9 +15,11 @@ "GameRulesAccessor$IntegerValueSimple", "MixinBlockEntity", "MixinBlockEntityExtension", + "MixinChunkSerializer", "MixinClientLevel", "MixinItemExtension", "MixinRegistryEntry", + "MixinWorldEvent", "MobSpawnSettingsBuilderAccessor" ], "injectors": { diff --git a/gradle.properties b/gradle.properties index 58bd3007..be9e6c22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ supported_version=1.16.4/5 archives_base_name=architectury archives_base_name_snapshot=architectury-snapshot -base_version=1.15 +base_version=1.16 maven_group=me.shedaniel fabric_loader_version=0.11.1 diff --git a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java index 3673b811..684145c7 100644 --- a/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java +++ b/testmod-common/src/main/java/me/shedaniel/architectury/test/events/DebugEvents.java @@ -217,6 +217,12 @@ public class DebugEvents { LightningEvent.STRIKE.register((bolt, level, pos, toStrike) -> { SINK.accept(bolt.getScoreboardName() + " struck at " + toShortString(pos) + logSide(level)); }); + ChunkEvent.LOAD_DATA.register((chunk, level, nbt) -> { + SINK.accept("Chunk loaded at x=" + chunk.getPos().x + ", z=" + chunk.getPos().z + " in dimension '" + level.dimension().location() + "'"); + }); + ChunkEvent.SAVE_DATA.register((chunk, level, nbt) -> { + SINK.accept("Chunk saved at x=" + chunk.getPos().x + ", z=" + chunk.getPos().z + " in dimension '" + level.dimension().location() + "'"); + }); } public static String toShortString(Vec3i pos) {