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",