From 860c9b6d211ea0d932392975091ac8638df01c4d Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 27 Dec 2019 01:26:18 -0400 Subject: [PATCH 1/3] adding stats test --- src/main/resources/assets/carpet/scripts/stats_test.sc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/resources/assets/carpet/scripts/stats_test.sc diff --git a/src/main/resources/assets/carpet/scripts/stats_test.sc b/src/main/resources/assets/carpet/scripts/stats_test.sc new file mode 100644 index 00000000..076e8eae --- /dev/null +++ b/src/main/resources/assets/carpet/scripts/stats_test.sc @@ -0,0 +1,5 @@ +__on_statistic(player, category, event, value) -> +( + print('__on_statistic(player, category, event, value) -> '); + print(' ['+join(', ',l(player, category, event, value))+']'); +); \ No newline at end of file From d7ca974f1ab7ba9a007fa4fee916655b5866e10d Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 27 Dec 2019 03:25:56 -0400 Subject: [PATCH 2/3] statistics, block placed and interacted events, stats event test --- .../java/carpet/fakes/StatTypeInterface.java | 6 +++ .../mixins/BlockItem_scarpetEventMixin.java | 27 +++++++++++++ ...PlayNetworkHandler_scarpetEventsMixin.java | 6 +-- ...InteractionManager_scarpetEventsMixin.java | 17 ++++++++ .../carpet/mixins/StatType_scarpetMixin.java | 22 ++++++++++ .../java/carpet/script/CarpetEventServer.java | 40 ++++++++++++++++++- .../java/carpet/script/CarpetExpression.java | 39 ++++++++++++++++++ .../carpet/script/CarpetScriptServer.java | 6 +++ .../java/carpet/script/value/EntityValue.java | 20 ++++++++++ .../java/carpet/script/value/ThreadValue.java | 5 +++ .../assets/carpet/scripts/event_test.sc | 27 +++++++++++-- src/main/resources/carpet.mixins.json | 2 + 12 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 src/main/java/carpet/fakes/StatTypeInterface.java create mode 100644 src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java create mode 100644 src/main/java/carpet/mixins/StatType_scarpetMixin.java diff --git a/src/main/java/carpet/fakes/StatTypeInterface.java b/src/main/java/carpet/fakes/StatTypeInterface.java new file mode 100644 index 00000000..e4196879 --- /dev/null +++ b/src/main/java/carpet/fakes/StatTypeInterface.java @@ -0,0 +1,6 @@ +package carpet.fakes; + +public interface StatTypeInterface +{ + boolean hasStatCreated(T key); +} diff --git a/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java b/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java new file mode 100644 index 00000000..e6f1092b --- /dev/null +++ b/src/main/java/carpet/mixins/BlockItem_scarpetEventMixin.java @@ -0,0 +1,27 @@ +package carpet.mixins; + +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.ActionResult; +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 static carpet.script.CarpetEventServer.Event.PLAYER_PLACES_BLOCK; + +@Mixin(BlockItem.class) +public class BlockItem_scarpetEventMixin +{ + @Inject(method = "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/block/Block;onPlaced(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;)V", + shift = At.Shift.AFTER + )) + private void afterPlacement(ItemPlacementContext context, CallbackInfoReturnable cir) + { + if (context.getPlayer() instanceof ServerPlayerEntity && PLAYER_PLACES_BLOCK.isNeeded()) + PLAYER_PLACES_BLOCK.onBlockPlaced((ServerPlayerEntity) context.getPlayer(), context.getBlockPos(), context.getHand(), context.getStack()); + } +} diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java index 6e560e89..48e3ab9e 100644 --- a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java @@ -24,7 +24,7 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_CLICKS_BLOCK; import static carpet.script.CarpetEventServer.Event.PLAYER_DEPLOYS_ELYTRA; import static carpet.script.CarpetEventServer.Event.PLAYER_DROPS_ITEM; import static carpet.script.CarpetEventServer.Event.PLAYER_DROPS_STACK; -import static carpet.script.CarpetEventServer.Event.PLAYER_INERACTSW_WITH_ENTITY; +import static carpet.script.CarpetEventServer.Event.PLAYER_INTERACTS_WITH_ENTITY; import static carpet.script.CarpetEventServer.Event.PLAYER_RELEASED_ITEM; import static carpet.script.CarpetEventServer.Event.PLAYER_RIDES; import static carpet.script.CarpetEventServer.Event.PLAYER_JUMPS; @@ -87,7 +87,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin @Inject(method = "onPlayerAction", at = @At( value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerInteractionManager;method_14263(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/server/network/packet/PlayerActionC2SPacket$Action;Lnet/minecraft/util/math/Direction;I)V", - shift = At.Shift.AFTER + shift = At.Shift.BEFORE )) private void onClicked(PlayerActionC2SPacket packet, CallbackInfo ci) { @@ -208,7 +208,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin )) private void onEntityInteract(PlayerInteractEntityC2SPacket playerInteractEntityC2SPacket_1, CallbackInfo ci) { - PLAYER_INERACTSW_WITH_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), playerInteractEntityC2SPacket_1.getHand()); + PLAYER_INTERACTS_WITH_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), playerInteractEntityC2SPacket_1.getHand()); } @Inject(method = "onPlayerInteractEntity", at = @At( diff --git a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java index 79e83cec..a9dc05cf 100644 --- a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java @@ -3,9 +3,15 @@ package carpet.mixins; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerInteractionManager; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -14,6 +20,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import static carpet.script.CarpetEventServer.Event.PLAYER_BREAK_BLOCK; +import static carpet.script.CarpetEventServer.Event.PLAYER_INTERACTS_WITH_BLOCK; + @Mixin(ServerPlayerInteractionManager.class) public class ServerPlayerInteractionManager_scarpetEventsMixin @@ -29,4 +37,13 @@ public class ServerPlayerInteractionManager_scarpetEventsMixin { PLAYER_BREAK_BLOCK.onBlockBroken(player, blockPos_1, blockState_1); } + + @Inject(method = "interactBlock", at = @At( + value = "RETURN", + ordinal = 2 + )) + private void onBlockActivated(PlayerEntity playerArg, World world, ItemStack stack, Hand hand, BlockHitResult blockHitResult, CallbackInfoReturnable cir) + { + PLAYER_INTERACTS_WITH_BLOCK.onBlockHit(player, hand, blockHitResult); + } } diff --git a/src/main/java/carpet/mixins/StatType_scarpetMixin.java b/src/main/java/carpet/mixins/StatType_scarpetMixin.java new file mode 100644 index 00000000..97b221ad --- /dev/null +++ b/src/main/java/carpet/mixins/StatType_scarpetMixin.java @@ -0,0 +1,22 @@ +package carpet.mixins; + +import carpet.fakes.StatTypeInterface; +import net.minecraft.stat.Stat; +import net.minecraft.stat.StatType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; + +@Mixin(StatType.class) +public class StatType_scarpetMixin implements StatTypeInterface +{ + @Shadow @Final private Map> stats; + + @Override + public boolean hasStatCreated(T key) + { + return this.stats.containsKey(key); + } +} diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index f132c638..dd8cae7b 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -296,6 +296,43 @@ public class CarpetEventServer }, player::getCommandSource); } }, + PLAYER_INTERACTS_WITH_BLOCK("player_interacts_with_block", new CallbackList(5)) + { + @Override + public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes) + { + handler.call( () -> + { + BlockPos blockpos = hitRes.getBlockPos(); + Direction enumfacing = hitRes.getSide(); + Vec3d vec3d = hitRes.getPos().subtract(blockpos.getX(), blockpos.getY(), blockpos.getZ()); + return Arrays.asList( + ((c, t) -> new EntityValue(player)), + ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand")), + ((c, t) -> new BlockValue(null, player.getServerWorld(), blockpos)), + ((c, t) -> new StringValue(enumfacing.getName())), + ((c, t) -> ListValue.of( + new NumericValue(vec3d.x), + new NumericValue(vec3d.y), + new NumericValue(vec3d.z) + )) + ); + }, player::getCommandSource); + } + }, + PLAYER_PLACES_BLOCK("player_places_block", new CallbackList(4)) + { + @Override + public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) + { + handler.call( () -> Arrays.asList( + ((c, t) -> new EntityValue(player)), + ((c, t) -> ListValue.fromItemStack(itemstack)), + ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand")), + ((c, t) -> new BlockValue(null, player.getServerWorld(), pos)) + ), player::getCommandSource); + } + }, PLAYER_BREAK_BLOCK("player_breaks_block",new CallbackList(2)) { @Override @@ -307,7 +344,7 @@ public class CarpetEventServer ), player::getCommandSource); } }, - PLAYER_INERACTSW_WITH_ENTITY("player_interacts_with_entity",new CallbackList(3)) + PLAYER_INTERACTS_WITH_ENTITY("player_interacts_with_entity",new CallbackList(3)) { @Override public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) @@ -465,6 +502,7 @@ public class CarpetEventServer public void onBlockAction(ServerPlayerEntity player, BlockPos blockpos, Direction facing) { } public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes) { } public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS) { } + public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) { } public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { } } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index c8e6b380..49467223 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -2,6 +2,7 @@ package carpet.script; import carpet.CarpetServer; import carpet.fakes.MinecraftServerInterface; +import carpet.fakes.StatTypeInterface; import carpet.helpers.FeatureGenerator; import carpet.mixins.ChunkTicketManager_scarpetMixin; import carpet.mixins.ServerChunkManager_scarpetMixin; @@ -68,11 +69,14 @@ import net.minecraft.server.world.ChunkTicket; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; +import net.minecraft.stat.Stat; +import net.minecraft.stat.StatType; import net.minecraft.state.StateFactory; import net.minecraft.state.property.Property; import net.minecraft.text.LiteralText; import net.minecraft.util.Clearable; import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.ChunkPos; @@ -3217,8 +3221,43 @@ public class CarpetExpression ((CarpetScriptHost)((CarpetContext)c).host).setGlobalState(tag, file); return (cc, tt) -> Value.NULL; }); + + this.expr.addLazyFunction("statistic", 3, (c, t, lv) -> + { + Value playerValue = lv.get(0).evalValue(c); + CarpetContext cc = (CarpetContext)c; + ServerPlayerEntity player = EntityValue.getPlayerByValue(cc.s.getMinecraftServer(), playerValue); + if (player == null) return LazyValue.NULL; + Identifier category; + Identifier statName; + try + { + category = new Identifier(lv.get(1).evalValue(c).getString()); + statName = new Identifier(lv.get(2).evalValue(c).getString()); + } + catch (InvalidIdentifierException e) + { + return LazyValue.NULL; + } + StatType type = Registry.STAT_TYPE.get(category); + if (type == null) + return LazyValue.NULL; + Stat stat = getStat(type, statName); + if (stat == null) + return LazyValue.NULL; + return (_c, _t) -> new NumericValue(player.getStatHandler().getStat(stat)); + }); } + private Stat getStat(StatType type, Identifier id) + { + T key = type.getRegistry().get(id); + if (key == null || !((StatTypeInterface)type).hasStatCreated(key)) + return null; + return type.getOrCreateStat(key); + } + + /** *

.

* @param expression expression diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 75968456..0f8639a8 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -46,6 +46,7 @@ public class CarpetScriptServer private static List bundledModuleData = new ArrayList(){{ add(new BundledModule("camera")); add(new BundledModule("event_test")); + add(new BundledModule("stats_test")); }}; public CarpetScriptServer() @@ -166,6 +167,11 @@ public class CarpetScriptServer //TODO add per player modules to support player actions better on a server name = name.toLowerCase(Locale.ROOT); ModuleInterface module = getModule(name); + if (module == null) + { + Messenger.m(source, "r Failed to add "+name+" app"); + return false; + } CarpetScriptHost newHost = createMinecraftScriptHost(name, module, perPlayer, source); if (newHost == null) { diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index f1e44d99..0489a28b 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -29,6 +29,7 @@ import net.minecraft.entity.mob.MobEntityWithAi; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundTag; import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; @@ -93,6 +94,25 @@ public class EntityValue extends Value return entity; } + public static ServerPlayerEntity getPlayerByValue(MinecraftServer server, Value value) + { + ServerPlayerEntity player = null; + if (value instanceof EntityValue) + { + Entity e = ((EntityValue) value).getEntity(); + if (e instanceof ServerPlayerEntity) + { + player = (ServerPlayerEntity) e; + } + } + else + { + String playerName = value.getString(); + player = server.getPlayerManager().getPlayer(playerName); + } + return player; + } + @Override public String getString() { diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 40500011..42682197 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -37,6 +37,11 @@ public class ThreadValue extends Value return taskFuture.getNow(Value.NULL).getString(); } + public Value getValue() + { + return taskFuture.getNow(Value.NULL); + } + @Override public boolean getBoolean() { diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index 4ac8d59e..80e2346e 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -160,7 +160,28 @@ __on_player_drops_stack(player) -> ( print('Player '+player+' drops current stack.') ); -__on_statistic(player, category, event, value) -> +__on_player_interacts_with_block(player, hand, block, face, hitvec) -> ( - print('__on_statistic(player, category, event, value) -> ['+join(', ',l(player, category, event, value))+']'); -); \ No newline at end of file + print(''); + print('__on_player_interacts_with_block(player, hand, block, face, hitvec) '); + print('block right clicked by player:'); + print(' - player: '+player); + print(' - hand: '+hand); + print(' - block: '+block+' at '+map(pos(block), str('%.2f',_))); + print(' - face: '+face); + print(' - hitvec: '+map(hitvec, str('%.2f',_))) +); +__on_player_places_block(player, item_tuple, hand, block) -> +( + l(item, count, nbt) = item_tuple || l('None', 0, null); + print(''); + print('__on_player_places_block(player, item_tuple, hand, block)'); + print('player places a block:'); + print(' - player: '+player); + print(' - block: '+block+' at '+map(pos(block), str('%.2f',_))); + print(' - hand: '+hand); + print(' - item:'); + print(' > name: '+item); + print(' > count: '+count); + print(' > nbt: '+nbt); +); diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 0ff0fc32..6191c3a2 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -141,9 +141,11 @@ "DamageTracker_scarpetEventMixin", "ServerPlayerEntity_scarpetEventMixin", "FallingBlockEntity_scarpetEventsMixin", + "BlockItem_scarpetEventMixin", "ChunkTicketManager_scarpetMixin", "ServerChunkManager_scarpetMixin", + "StatType_scarpetMixin", "PistonBlock_movableTEMixin", From 4f52a04d84b846c37b4f866d27e19b2d8dd97487 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 27 Dec 2019 11:23:57 -0400 Subject: [PATCH 3/3] docs --- .../java/carpet/script/CarpetExpression.java | 48 ++++++++++++-- src/main/java/carpet/script/Expression.java | 65 ++++++++++++++++++- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 49467223..2ea87de9 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -1745,6 +1745,9 @@ public class CarpetExpression *

String with gamemode, or null if not a player.

*

query(e,'gamemode_id')

*

Good'ol gamemode id, or null if not a player.

+ *

query(e,'permission_level')

+ *

Player's permission level, or null if not applicable for this entity.

+ * *

query(e,'effect',name?)

*

Without extra arguments, it returns list of effect active on a living entity. * Each entry is a triple of short effect name, amplifier, and remaining duration. @@ -1836,6 +1839,12 @@ public class CarpetExpression *

Make noises.

*

modify(e, 'ai', boolean)

*

If called with false value, it will disable AI in the mob. true will enable it again.

+ *

modify(e, 'no_clip', boolean)

+ *

Sets if the entity obeys any collisions, including collisions with the terrain and basic physics. + * Not affecting players, since they are controlled client side

+ *

modify(e, 'effect', name, duration, amplifier?, show_particles?, show_icon?)

+ *

Applies status effect to the living entity. Takes several optional parameters, which default to 0, + * true and true.

*

modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)

*

Sets AI to stay around the home position, within distance blocks from it. distance * defaults to 16 blocks. null removes it. May not work fully with mobs that have this AI built in, like @@ -2640,7 +2649,7 @@ public class CarpetExpression *

load_app_data(), load_app_data(file)

*

Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory * managed and buffered / throttled NBT tag. With a file name - reads explicitly a file with that name from the scripts - * floder.

+ * folder.

*

You can use app data to save non-vanilla information separately from the world and other scripts.

*

store_app_data(tag), store_app_data(tag, file)

*

Stores the app data associated with the app from the world /scripts folder. With the file parameter @@ -2665,6 +2674,25 @@ public class CarpetExpression *

schedule(delay, function, args...)

*

Schedules a user defined function to run with a specified delay ticks of delay. * Scheduled functions run at the end of the tick, and they will run in order they were scheduled.

+ *

statistic(player, category, entry)

+ *

Queries in-game statistics for certain values. Categories include:

+ *
    + *
  • mined: blocks mined
  • + *
  • crafted: items crafted
  • + *
  • used: items used
  • + *
  • broken: items broken
  • + *
  • picked_up: items picked up
  • + *
  • dropped: items dropped
  • + *
  • killed: mobs killed
  • + *
  • killed_by: blocks mined
  • + *
  • custom: various random stats
  • + *
+ *

For the options of entry, consult your statistics page, or give it a guess.

+ *

The call will return null if the statistics options are incorrect, or player didn't get these + * in their history. If the player encountered the statisitic, or game created for him empty one, it will + * return a number. Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. + * With null response it could either mean your input is wrone, or statistic has effectively + * a value of 0.

*

plop(pos, what)

*

Plops a structure or a feature at a given pos, so block, triple position coordinates * or a list of coordinates. To what gets plopped and exactly where it often depends on the @@ -3010,12 +3038,12 @@ public class CarpetExpression }); //"overidden" native call to cancel if on main thread - this.expr.addLazyFunction("join_task", 1, (c, t, lv) -> { + this.expr.addLazyFunction("task_join", 1, (c, t, lv) -> { if (((CarpetContext)c).s.getMinecraftServer().isOnThread()) - throw new InternalExpressionException("'join_task' cannot be called from main thread to avoid deadlocks"); + throw new InternalExpressionException("'task_join' cannot be called from main thread to avoid deadlocks"); Value v = lv.get(0).evalValue(c); if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'join_task' could only be used with a task value"); + throw new InternalExpressionException("'task_join' could only be used with a task value"); Value ret = ((ThreadValue) v).join(); return (_c, _t) -> ret; }); @@ -3483,7 +3511,11 @@ public class CarpetExpression * should be running at startup. WARNING: all apps will run once at startup anyways, so be aware that their actions * that are called statically, will be performed once anyways. * - *

Unloading an app removes all of its state from the game, disables commands, and removes bounded events

+ *

Unloading an app removes all of its state from the game, disables commands, and removes bounded events, + * and saves its global state. If more + * cleanup is needed, one can define an __on_close() function which will be executed when the module is unloaded, + * or server is closing, or crashing. However, there is no need to do that explicitly for the things mentioned + * in the previous statement.

*

Scripts can be loaded in shared(global) and player mode. Default is player, so all globals and stored functions are * individual for each player, meaning scripts don't need to worry about making sure they store some intermittent data * for each player independently. In global mode - all global values and stored functions are shared among all players. @@ -3523,8 +3555,10 @@ public class CarpetExpression * __on_player_releases_item(player, item_tuple, hand) // client action (e.g. bow) * __on_player_finishes_using_item(player, item_tuple, hand)) // server action (e.g. food), called item is from before it is used. * __on_player_clicks_block(player, block, face) // left click attack on a block - * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) * __on_player_breaks_block(player, block) // called after block is broken (the caller receives previous blockstate) + * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) // player right clicks block with anything + * __on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block + * __on_player_places_block(player, item_tuple, hand, block) // player have just placed the block. * __on_player_interacts_with_entity(player, entity, hand) * __on_player_attacks_entity(player, entity) * __on_player_rides(player, forward, strafe, jumping, sneaking) @@ -3537,6 +3571,8 @@ public class CarpetExpression * __on_player_stops_sprinting(player) * __on_player_drops_item(player) * __on_player_drops_stack(player) + * + * __on_statistic(player, category, event, value) // player statistic changes * *

/script event command

*

used to display current events and bounded functions. use add_to ro register new event, or remove_from diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 0bc27604..1aecd2a9 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -60,7 +60,7 @@ import static java.lang.Math.min; /** - *

Fundamental components of scarpet programming language (version 1.6).

+ *

Fundamental components of scarpet programming language (towards version 1.7).

* *

Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide * the ability to write custom programs to run within Minecraft and @@ -2613,6 +2613,59 @@ public class Expression implements Cloneable * * *


+ *

Threading and Parallel Execution

+ *

Scarpet allows to run threads of execution in parallel to the main script execution thread. In Minecraft, the main + * thread scripts are executed on the main server thread. Since Minecraft is inherently NOT thread safe, it is not that + * beneficial to parallel execution in order to access world resources faster. Both + * GetBlockState and setBlockState are not thread safe and require the execution to park on the server thread, + * where these requests can be executed in the off-tick time in between ticks that didn't take 50ms. There are however + * benefits of running things in parallel, like fine time control not relying on the tick clock, or running things independent + * on each other. You can still run your actions on tick-by-tick basis, either taking control of the execution using + * game_tick() API function (nasty solution), or scheduling tick using schedule() function (much nicer solution), + * but threading often gives the neatest solution to solve problems in parallel (see scarpet camera). + *

+ *

Due to limitations with the game, there are some limits to the threading as well. You cannot for instance join_task() at all + * from the main script and server thread, because any use of Minecraft specific function that require any world access, will + * require to park and join on the main thread to get world access, meaning that calling join on that task would inevitably lead to a + * typical deadlock. You can still join tasks from other threads, just because the only possibility of a deadlock in this case would + * come explicitly from your bad code, not the internal world access behaviour. Some things tough like player or entity manipulation, + * can be effectively parallelized.

+ *

task(function, ?args...., ?executor)

+ *

Creates and runs a parallel task, returning the handle to the task object. Task will return the return value of the function + * when its completed, or will return null immediately if task is still in progress, so grabbing a value of + * a task object is non-blocking. Function can be either function value, or function lambda, or a name of an existing defined function. + * In case function needs arguments to be called with, they should be supplied after the function name, or value. + * Optional executor identifier is to place the task in a specific queue identified by this value. The default + * thread value is the null thread. There is no limits on number of parallel tasks for any executor, so using different queues is solely + * for synchronization purposes. Also, since task function knows how many extra arguments it requires, the use of + * tailing optional parameter for the custom executor thread pool is not ambiguous. + *

+ *
+     * task( _() -> print('Hello Other World') )  => Runs print command on a separate thread
+     * foo(a, b) -> print(a+b); task('foo',2,2)  => Uses existing function definition to start a task
+     * task('foo',3,5,'temp');  => runs function foo with a different thread executor, identified as 'temp'
+     * a = 3; task( _(outer(a), b) -> foo(a,b), 5, 'temp')  => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
+     * 
+ *

task_count(executor?)

+ *

If no argument provided, returns total number of tasks being executed in parallel at this moment using scarpet threading system. + * If the executor is provided, returns number of active tasks for that provider. Use task_count(null) to + * get the task count of the default executor only.

+ * + *

task_value(task)

+ *

Returns the task return value, or null if task hasn't finished yet. Its a non-blocking operation. + * Unlike join_task, can be called on any task at any point

+ * + *

task_join(task)

+ *

Waits for the task completion and returns its computed value. If the task has already finished returns it immediately. + * Unless taking the task value directly, i.e. via task_value, this operation is blocking. Since Minecraft has + * a limitation that all world access operations have to be performed on the main game thread in the off-tick time, joining any tasks that use + * Minecraft API from the main thread would mean automatic lock, so joining from the main thread is not allowed. + * Join tasks from other threads, if you really need to, or communicate asynchronously with the task via globals or + * function data / arguments to monitor its progress, communicate, get partial results, or signal termination.

+ * + *

task_completed(task)

+ *

Returns true if task has completed, or false otherwise.

+ * *

Auxiliary functions

* *

lower(expr), upper(expr), title(expr)

@@ -3039,9 +3092,15 @@ public class Expression implements Cloneable return new NumericValue(ThreadValue.taskCount()); }); - addUnaryFunction("join_task", (v) -> { + addUnaryFunction("task_value", (v) -> { if (!(v instanceof ThreadValue)) - throw new InternalExpressionException("'join_task' could only be used with a task value"); + throw new InternalExpressionException("'task_value' could only be used with a task value"); + return ((ThreadValue) v).getValue(); + }); + + addUnaryFunction("task_join", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'task_join' could only be used with a task value"); return ((ThreadValue) v).join(); });