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();
});