From b1b7956ea9d4166361df87fe3769d7f6ed48b1f9 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 26 Jun 2021 23:20:13 -0300 Subject: [PATCH] added chunk_loaded event and fixed handling these events on 1.17 --- docs/scarpet/api/Events.md | 13 +++- docs/scarpet/resources/editors/idea/4.txt | 3 +- .../scarpet/resources/editors/npp/scarpet.xml | 1 + ...hunkStorage_scarpetChunkCreationMixin.java | 67 +++++++++++++++---- .../java/carpet/script/CarpetEventServer.java | 20 ++++-- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index e0a6ffa3..fb72e013 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -70,10 +70,17 @@ Duplicate of `tick`, just automatically located in the nether. Use `__on_tick() ### `__on_tick_ender()` (Deprecated) Duplicate of `tick`, just automatically located in the end. Use `__on_tick() -> in_dimension('end', ... ` instead. -### `__on_chunk_generated(x,z)` +### `__on_chunk_generated(x, z)` Called right after a chunk at a given coordinate is full generated. `x` and `z` correspond -to the lowest x and z coords in the chunk. Event may (or may not) work with Optifine installed -at the same time. +to the lowest x and z coords in the chunk. Handling of this event is scheduled as an off-tick task happening after the +chunk is confirmed to be generated and loaded to the game, so +handling of this event is not technically guaranteed, if the game crashes while players are moving for example, and can +be missed on the next start, if the game successfully saves it and fails to execute all remaining tasks. In normal operation +this should not happen, but be warned. + +### `__on_chunk_loaded(x, z)` +Called right after a chunk at a given coordinate is loaded. All newly generated chunks are loaded as well. + `x` and `z` correspond to the lowest x and z coordinates in the chunk. ### `__on_lightning(block, mode)` Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt index 388185dc..0eb4b964 100644 --- a/docs/scarpet/resources/editors/idea/4.txt +++ b/docs/scarpet/resources/editors/idea/4.txt @@ -1,6 +1,7 @@ __command() -> __config() -> __on_chunk_generated(x, z) -> +__on_chunk_loaded(x, z) -> __on_close() -> __on_player_attacks_entity(player, entity) -> __on_player_breaks_block(player, block) -> @@ -34,7 +35,7 @@ __on_player_wakes_up(player) -> __on_player_escapes_sleep(player) -> __on_statistic(player, category, event, value) -> __on_start() -> -__on_tick() -> +__on_tick() -> __on_tick_ender() -> __on_tick_nether() -> __on_player_takes_damage(player, amount, source, source_entity) -> diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 7a8f00c4..8ca6b850 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -100,6 +100,7 @@ __command __config __on_chunk_generated + __on_chunk_loaded __on_close __on_player_attacks_entity __on_player_breaks_block diff --git a/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java index 54211a59..0f7fee88 100644 --- a/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java +++ b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java @@ -12,6 +12,8 @@ import java.util.concurrent.Future; import java.util.function.IntFunction; import java.util.stream.Collectors; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.ServerTask; import org.apache.commons.lang3.tuple.Pair; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -50,6 +52,7 @@ import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.storage.RegionFile; import static carpet.script.CarpetEventServer.Event.CHUNK_GENERATED; +import static carpet.script.CarpetEventServer.Event.CHUNK_LOADED; @Mixin(ThreadedAnvilChunkStorage.class) public abstract class ThreadedAnvilChunkStorage_scarpetChunkCreationMixin implements ThreadedAnvilChunkStorageInterface @@ -98,22 +101,62 @@ public abstract class ThreadedAnvilChunkStorage_scarpetChunkCreationMixin implem @Shadow protected abstract Iterable entryIterator(); - //in method_20617 - //method_19534(Lnet/minecraft/server/world/ChunkHolder;Lnet/minecraft/world/chunk/Chunk;)Ljava/util/concurrent/CompletableFuture; - // incmopatibility with optifine makes this mixin fail. - // lambda for convertToFullChunk + + ThreadLocal generated = ThreadLocal.withInitial(() -> null); + + // in convertToFullChunk + // fancier version of the one below, ensuring that the event is triggered when the chunk is actually loaded. @SuppressWarnings("UnresolvedMixinReference") - @Inject(method = "method_19534", require = 0, at = @At( - value = "INVOKE", - target = "Lnet/minecraft/server/world/ThreadedAnvilChunkStorage;convertToFullChunk(Lnet/minecraft/server/world/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;", - shift = At.Shift.AFTER - )) - private void onChunkGenerated(final ChunkHolder chunkHolder, final Chunk chunk, final CallbackInfoReturnable cir) + @Inject(method = "method_20460", at = @At("HEAD")) + private void onChunkGeneratedStart(ChunkHolder chunkHolder, Either chunk, CallbackInfoReturnable>> cir) { - if (CHUNK_GENERATED.isNeeded()) - this.world.getServer().execute(() -> CHUNK_GENERATED.onChunkGenerated(this.world, chunk)); + if (CHUNK_GENERATED.isNeeded() || CHUNK_LOADED.isNeeded()) + { + generated.set(chunkHolder.getCurrentChunk().getStatus() != ChunkStatus.FULL); + } + else + { + generated.set(null); + } } + @SuppressWarnings("UnresolvedMixinReference") + @Inject(method = "method_20460", at = @At("RETURN")) + private void onChunkGeneratedEnd(ChunkHolder chunkHolder, Either chunk, CallbackInfoReturnable>> cir) + { + Boolean localGenerated= generated.get(); + if (localGenerated != null) + { + MinecraftServer server = this.world.getServer(); + int ticks = server.getTicks(); + ChunkPos chpos = chunkHolder.getPos(); + // need to send these because if an app does something with that event, it may lock the thread + // so better be safe and schedule it for later, aSaP + if (CHUNK_GENERATED.isNeeded() && localGenerated) + server.send(new ServerTask(ticks, () -> CHUNK_GENERATED.onChunkEvent(this.world, chpos, true))); + if (CHUNK_LOADED.isNeeded()) + server.send(new ServerTask(ticks, () -> CHUNK_LOADED.onChunkEvent(this.world, chpos, localGenerated))); + } + } + + /* simple but a version that doesn't guarantee that the chunk is actually loaded + @Inject(method = "convertToFullChunk", at = @At("HEAD")) + private void onChunkGeneratedEnd(ChunkHolder chunkHolder, CallbackInfoReturnable>> cir) + { + if (CHUNK_GENERATED.isNeeded() && chunkHolder.getCurrentChunk().getStatus() != ChunkStatus.FULL) + { + ChunkPos chpos = chunkHolder.getPos(); + this.world.getServer().execute(() -> CHUNK_GENERATED.onChunkEvent(this.world, chpos, true)); + } + if (CHUNK_LOADED.isNeeded()) + { + boolean generated = chunkHolder.getCurrentChunk().getStatus() != ChunkStatus.FULL; + ChunkPos chpos = chunkHolder.getPos(); + this.world.getServer().execute(() -> CHUNK_LOADED.onChunkEvent(this.world, chpos, generated)); + } + } + */ + @Unique private void addTicket(final ChunkPos pos, final ChunkStatus status) { // UNKNOWN diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 73205624..c92620b6 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -45,7 +45,6 @@ import net.minecraft.util.registry.RegistryKey; import net.minecraft.village.Merchant; import net.minecraft.village.TradeOffer; import net.minecraft.world.World; -import net.minecraft.world.chunk.Chunk; import net.minecraft.world.explosion.Explosion; import org.apache.commons.lang3.tuple.Pair; @@ -417,12 +416,23 @@ public class CarpetEventServer public static final Event CHUNK_GENERATED = new Event("chunk_generated", 2, true) { @Override - public void onChunkGenerated(ServerWorld world, Chunk chunk) + public void onChunkEvent(ServerWorld world, ChunkPos chPos, boolean generated) { handler.call( () -> { - ChunkPos pos = chunk.getPos(); - return Arrays.asList(new NumericValue(pos.x << 4), new NumericValue(pos.z << 4)); + return Arrays.asList(new NumericValue(chPos.x << 4), new NumericValue(chPos.z << 4)); + }, () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world) + ); + } + }; + public static final Event CHUNK_LOADED = new Event("chunk_loaded", 2, true) + { + @Override + public void onChunkEvent(ServerWorld world, ChunkPos chPos, boolean generated) + { + handler.call( () -> + { + return Arrays.asList(new NumericValue(chPos.x << 4), new NumericValue(chPos.z << 4)); }, () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world) ); } @@ -1050,7 +1060,7 @@ public class CarpetEventServer public boolean deprecated() {return false;} //stubs for calls just to ease calls in vanilla code so they don't need to deal with scarpet value types public void onTick() { } - public void onChunkGenerated(ServerWorld world, Chunk chunk) { } + public void onChunkEvent(ServerWorld world, ChunkPos chPos, boolean generated) { } public void onPlayerEvent(ServerPlayerEntity player) { } public void onPlayerMessage(ServerPlayerEntity player, String message) { } public void onPlayerStatistic(ServerPlayerEntity player, Stat stat, int amount) { }