From 061e718a8930f3d466a03555a1f187c9186f17b0 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 24 Feb 2020 02:29:59 -0400 Subject: [PATCH] added __on_chunk_generated event and attempted to illegalize global events in player scoped apps. --- ...hunkStorage_scarpetChunkCreationMixin.java | 34 +++++++ .../java/carpet/script/CarpetEventServer.java | 97 +++++++++++++------ .../java/carpet/script/CarpetExpression.java | 51 +++++++++- src/main/resources/carpet.mixins.json | 2 + 4 files changed, 150 insertions(+), 34 deletions(-) create mode 100644 src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java diff --git a/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java new file mode 100644 index 00000000..ba8403c0 --- /dev/null +++ b/src/main/java/carpet/mixins/ThreadedAnvilChunkStorage_scarpetChunkCreationMixin.java @@ -0,0 +1,34 @@ +package carpet.mixins; + +import net.minecraft.server.world.ChunkHolder; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.world.chunk.Chunk; +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 java.util.concurrent.CompletableFuture; + +import static carpet.script.CarpetEventServer.Event.CHUNK_GENERATED; + +@Mixin(ThreadedAnvilChunkStorage.class) +public class ThreadedAnvilChunkStorage_scarpetChunkCreationMixin +{ + @Shadow @Final private ServerWorld world; + + //in method_20617 + @Inject(method = "method_19534", 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(ChunkHolder chunkHolder, Chunk chunk, CallbackInfoReturnable cir) + { + if (CHUNK_GENERATED.isNeeded()) + world.getServer().execute( () -> CHUNK_GENERATED.onChunkGenerated(world, chunk) ); + } +} diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 0e822279..1c08bfc8 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -14,15 +14,18 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; import net.minecraft.stat.Stat; import net.minecraft.stat.Stats; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; +import net.minecraft.world.chunk.Chunk; import net.minecraft.world.dimension.DimensionType; import org.apache.commons.lang3.tuple.Pair; @@ -36,6 +39,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -122,7 +126,7 @@ public class CarpetEventServer callList.removeIf(call -> !CarpetServer.scriptServer.runas(source, call.host, call.function, argv)); // this actually does the calls } } - public boolean addEventCall(String hostName, String funName) + public boolean addEventCall(String hostName, String funName, Function verifier) { ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName); if (host == null) @@ -130,6 +134,11 @@ public class CarpetEventServer // impossible call to add return false; } + if (!verifier.apply(host)) + { + // host cannot use that event, its global event on player based app + return false; + } FunctionValue udf = host.getFunction(funName); if (udf == null || udf.getArguments().size() != reqArgs) { @@ -168,7 +177,7 @@ public class CarpetEventServer public enum Event { - TICK("tick", new CallbackList(0)) + TICK("tick", 0, true) { @Override public void onTick() @@ -179,7 +188,7 @@ public class CarpetEventServer ); } }, - NETHER_TICK("tick_nether",new CallbackList(0)) + NETHER_TICK("tick_nether", 0, true) { @Override public void onTick() @@ -190,7 +199,7 @@ public class CarpetEventServer ); } }, - ENDER_TICK("tick_ender",new CallbackList(0)) + ENDER_TICK("tick_ender", 0, true) { @Override public void onTick() @@ -201,7 +210,24 @@ public class CarpetEventServer ); } }, - PLAYER_JUMPS("player_jumps", new CallbackList(1)) + CHUNK_GENERATED("chunk_generated", 2, true) + { + @Override + public void onChunkGenerated(ServerWorld world, Chunk chunk) + { + handler.call( () -> + { + ChunkPos pos = chunk.getPos(); + return Arrays.asList( + ((c, t) -> new NumericValue(pos.x << 4)), + ((c, t) -> new NumericValue(pos.z << 4)) + ); + }, () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world) + ); + } + }, + + PLAYER_JUMPS("player_jumps", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -209,7 +235,7 @@ public class CarpetEventServer handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_DEPLOYS_ELYTRA("player_deploys_elytra",new CallbackList(1)) + PLAYER_DEPLOYS_ELYTRA("player_deploys_elytra", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -217,7 +243,7 @@ public class CarpetEventServer handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_WAKES_UP("player_wakes_up",new CallbackList(1)) + PLAYER_WAKES_UP("player_wakes_up", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -225,7 +251,7 @@ public class CarpetEventServer handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_RIDES("player_rides",new CallbackList(5)) + PLAYER_RIDES("player_rides", 5, false) { @Override public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) @@ -239,7 +265,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_USES_ITEM("player_uses_item",new CallbackList(3)) + PLAYER_USES_ITEM("player_uses_item", 3, false) { @Override public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) @@ -255,7 +281,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_CLICKS_BLOCK("player_clicks_block",new CallbackList(3)) + PLAYER_CLICKS_BLOCK("player_clicks_block", 3, false) { @Override public void onBlockAction(ServerPlayerEntity player, BlockPos blockpos, Direction facing) @@ -270,7 +296,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_RIGHT_CLICKS_BLOCK("player_right_clicks_block",new CallbackList(6)) + PLAYER_RIGHT_CLICKS_BLOCK("player_right_clicks_block", 6, false) { @Override public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes)//ItemStack itemstack, Hand enumhand, BlockPos blockpos, Direction enumfacing, Vec3d vec3d) @@ -296,7 +322,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_INTERACTS_WITH_BLOCK("player_interacts_with_block", new CallbackList(5)) + PLAYER_INTERACTS_WITH_BLOCK("player_interacts_with_block", 5, false) { @Override public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes) @@ -320,7 +346,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_PLACES_BLOCK("player_places_block", new CallbackList(4)) + PLAYER_PLACES_BLOCK("player_places_block", 4, false) { @Override public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) @@ -333,7 +359,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_BREAK_BLOCK("player_breaks_block",new CallbackList(2)) + PLAYER_BREAK_BLOCK("player_breaks_block", 2, false) { @Override public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS) @@ -344,7 +370,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_INTERACTS_WITH_ENTITY("player_interacts_with_entity",new CallbackList(3)) + PLAYER_INTERACTS_WITH_ENTITY("player_interacts_with_entity", 3, false) { @Override public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) @@ -356,7 +382,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_ATTACKS_ENTITY("player_attacks_entity",new CallbackList(2)) + PLAYER_ATTACKS_ENTITY("player_attacks_entity", 2, false) { @Override public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) @@ -367,7 +393,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_STARTS_SNEAKING("player_starts_sneaking",new CallbackList(1)) + PLAYER_STARTS_SNEAKING("player_starts_sneaking", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -375,7 +401,7 @@ public class CarpetEventServer handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_STOPS_SNEAKING("player_stops_sneaking",new CallbackList(1)) + PLAYER_STOPS_SNEAKING("player_stops_sneaking", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -383,7 +409,7 @@ public class CarpetEventServer handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_STARTS_SPRINTING("player_starts_sprinting",new CallbackList(1)) + PLAYER_STARTS_SPRINTING("player_starts_sprinting", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -391,7 +417,7 @@ public class CarpetEventServer handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_STOPS_SPRINTING("player_stops_sprinting",new CallbackList(1)) + PLAYER_STOPS_SPRINTING("player_stops_sprinting", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -400,7 +426,7 @@ public class CarpetEventServer } }, - PLAYER_RELEASED_ITEM("player_releases_item",new CallbackList(3)) + PLAYER_RELEASED_ITEM("player_releases_item", 3, false) { @Override public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) @@ -416,7 +442,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_FINISHED_USING_ITEM("player_finishes_using_item",new CallbackList(3)) + PLAYER_FINISHED_USING_ITEM("player_finishes_using_item", 3, false) { @Override public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) @@ -432,7 +458,7 @@ public class CarpetEventServer }, player::getCommandSource); } }, - PLAYER_DROPS_ITEM("player_drops_item", new CallbackList(1)) + PLAYER_DROPS_ITEM("player_drops_item", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -440,7 +466,7 @@ public class CarpetEventServer handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - PLAYER_DROPS_STACK("player_drops_stack", new CallbackList(1)) + PLAYER_DROPS_STACK("player_drops_stack", 1, false) { @Override public void onPlayerEvent(ServerPlayerEntity player) @@ -448,7 +474,7 @@ public class CarpetEventServer handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } }, - STATISTICS("statistic", new CallbackList(4)) + STATISTICS("statistic", 4, false) { private Identifier getStatId(Stat stat) { @@ -484,10 +510,12 @@ public class CarpetEventServer } }}; public final CallbackList handler; - Event(String name, CallbackList eventHandler) + public final boolean globalOnly; + Event(String name, int reqArgs, boolean isGlobalOnly) { this.name = name; - this.handler = eventHandler; + this.handler = new CallbackList(reqArgs); + this.globalOnly = isGlobalOnly; } public boolean isNeeded() { @@ -495,6 +523,7 @@ public class CarpetEventServer } //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 onPlayerEvent(ServerPlayerEntity player) { } public void onPlayerStatistic(ServerPlayerEntity player, Stat stat, int amount) { } public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { } @@ -544,12 +573,22 @@ public class CarpetEventServer { return false; } - return Event.byName.get(event).handler.addEventCall(host, funName); + Event ev = Event.byName.get(event); + return ev.handler.addEventCall(host, funName, h -> canAddEvent(ev, h)); } public boolean addEventDirectly(String event, ScriptHost host, FunctionValue function) { - return Event.byName.get(event).handler.addEventCallDirect(host, function); + Event ev = Event.byName.get(event); + // config can be analyzed after code is read, so this will always be player scope + //if (!canAddEvent(ev, host)) + // throw new InternalExpressionException("Global event "+event+" can only be added to apps with global scope"); + return ev.handler.addEventCallDirect(host, function); + } + + private boolean canAddEvent(Event event, ScriptHost host) + { + return !(event.globalOnly && host.perUser); } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index cf6efa92..345f4b77 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -492,6 +492,34 @@ public class CarpetExpression * set(x,y,z,block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing * set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data * + *

without_updates(expr)

+ *

Evaluates subexpression without causing updates when blocks change in the world

+ *

Consider following scenario: We would like to generate a bunch of terrain in a flat world + * following a perling noise generator. The following code causes cascading effect as blocks placed on chunk borders will + * cause other chunks to get loaded to full, thus generated:

+ *
+     * __config() -> m(l('scope', 'global'));
+     * __on_chunk_generated(x, z) -> (
+     *   scan(x,0,z,0,0,0,15,15,15,
+     *     if (perlin(_x/16, _y/8, _z/16) > _y/16,
+     *       set(_, 'black_stained_glass');
+     *     )
+     *   )
+     * )
+     * 
+ *

The following addition resolves this issue, by not allowing block updates pass chunk borders:

+ *
+     * __config() -> m(l('scope', 'global'));
+     * __on_chunk_generated(x, z) -> (
+     *   without_updates(
+     *     scan(x,0,z,0,0,0,15,15,15,
+     *       if (perlin(_x/16, _y/8, _z/16) > _y/16,
+     *         set(_, 'black_stained_glass');
+     *       )
+     *     )
+     *   )
+     * )
+     * 
*

place_item(item, pos, facing?, sneak?)

*

Places a given item in the world like it was placed by a player. Item names are default minecraft item name, * less the minecraft prefix. Default facing is 'up', but there are other options: 'down', 'north', 'east', @@ -1217,6 +1245,21 @@ public class CarpetExpression return true; })); + this.expr.addLazyFunction("without_updates", 1, (c, t, lv) -> + { + boolean previous = CarpetSettings.impendingFillSkipUpdates; + try + { + CarpetSettings.impendingFillSkipUpdates = true; + Value ret = lv.get(0).evalValue(c, t); + return (cc, tt) -> ret; + } + finally + { + CarpetSettings.impendingFillSkipUpdates = previous; + } + }); + this.expr.addLazyFunction("set", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; @@ -1249,7 +1292,6 @@ public class CarpetExpression BlockPos targetPos = targetLocator.block.getPos(); cc.s.getMinecraftServer().submitAndJoin( () -> { - CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates; Clearable.clear(world.getBlockEntity(targetPos)); world.setBlockState(targetPos, finalSourceBlockState, 2); if (data != null) @@ -1265,7 +1307,6 @@ public class CarpetExpression be.markDirty(); } } - CarpetSettings.impendingFillSkipUpdates = false; }); Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); return (c_, t_) -> retval; @@ -1431,9 +1472,7 @@ public class CarpetExpression { if (placementState.canPlaceAt(cc.s.getWorld(), where)) { - CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates; cc.s.getWorld().setBlockState(where, placementState, 2); - CarpetSettings.impendingFillSkipUpdates = false; BlockSoundGroup blockSoundGroup = placementState.getSoundGroup(); cc.s.getWorld().playSound(null, where, blockSoundGroup.getPlaceSound(), SoundCategory.BLOCKS, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F); return (_c, _t) -> Value.TRUE; @@ -3981,7 +4020,8 @@ public class CarpetExpression * starts with __on_... and has the required arguments, will be bound automatically. * In case of player specific modules, all player action events will be directed to the appropriate player space, and * all tick events will be executed in the global context, so its not a good idea to mix these two, so use either of these, - * or use commands to call tick events directly, or handle player specific data inside a module.

+ * or use commands to call tick events directly, or handle player specific data inside a module. In near future using + * global events (on_tick, on_chunk_generated) will be not allowed in player scoped apps.

*

*

Event list

*

Here is a list of events that can be handled by scarpet. This list includes prefixes required by modules to @@ -3990,6 +4030,7 @@ public class CarpetExpression * __on_tick() // can access blocks and entities in the overworld * __on_tick_nether() // can access blocks and entities in the nether * __on_tick_ender() // can access blocks and entities in the end + * __on_chunk_generated(x,z) // called after a chunk is promoted to the full chunk, prodiving lowest x and z coords in the chunk * * // player specific callbacks * __on_player_uses_item(player, item_tuple, hand) // right click action diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 19b88f9f..816fb922 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -26,6 +26,8 @@ "ServerChunkManager_tickMixin", "ThreadedAnvilChunkStorage_tickMixin", + "ThreadedAnvilChunkStorage_scarpetChunkCreationMixin", + "MobEntityMixin", "ServerWorld_unloadedEntityMixin", "EntityTrackerEntry_leashFixMixin",