diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 99523a4f..a0bac5ea 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -906,8 +906,11 @@ sleep(50)
### `time()`
-Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`.
-It returns a float, which has 1 microsecond precision (0.001 ms)
+Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`, which varies from system to
+system and from Java to Java. This measure should NOT be used to determine the current (date)time, but to measure
+durations of things.
+it returns a float with time in milliseconds (ms) for convenience and microsecond (μs) resolution for sanity.
+
start_time = time();
@@ -1513,7 +1516,7 @@ range(5,10) => [5, 6, 7, 8, 9]
range(20, 10, -2) => [20, 18, 16, 14, 12]
-### `element(list, index)` (deprecated)
+### `element(list, index)(deprecated)`
Legacy support for older method that worked only on lists. Please use `get(...)` for equivalent support, or `.`
operator. Also previous unique behaviours with `put` on lists has been removed to support all type of
@@ -3044,6 +3047,8 @@ __on_player_starts_sprinting(player)
__on_player_stops_sprinting(player)
__on_player_drops_item(player)
__on_player_drops_stack(player)
+__on_player_connects(player)
+__on_player_disconnects(player, reason)
__on_statistic(player, category, event, value) // player statistic changes
@@ -3103,24 +3108,77 @@ and `voice`. `pos` can be either a block, triple of coords, or a list of thee nu
## Particles
-### `particle(name, pos, count?. spread?, speed?, playername?)`
+### `particle(name, pos, count?. spread?, speed?, player?)`
Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed`
of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle`
command on details on those options. Valid particle names are
for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`.
-### `particle_line(name, pos, pos2, density?)`
+### `particle_line(name, pos, pos2, density?, player?)`
Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far
-apart you would want particles to appear, so `0.1` means one every 10cm.
+apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only
+that player will receive particles.
-### `particle_rect(name, pos, pos2, density?)`
-Renders a cuboid of particles between points `pos` and `pos2` with supplied density.
+### `particle_box(name, pos, pos2, density?, player?)`
+### `particle_rect` (deprecated)
+
+Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is
+supplied, only that player will receive particles.
## Markers
+### `draw_shape(shape, duration, key?, value?, ... )`, `draw_shape(shape, duration, l(key?, value?, ... ))`, `draw_shape(shape, duration, attribute_map)`
+
+Draws a shape in the world that will expire in `duration` ticks. Other attributes of the shape should be provided as
+consecutive key - value argument pairs, either as next arguments, or packed in a list, or supplied as a proper key-value
+`map`. Arguments may include shared shape attributes, which are all optional, as well as shape-specific attributes, that
+could be either optional or required. Shapes will draw properly on all carpet clients. Other connected players that don't
+have carpet installed will still be able to see the required shapes in the form of dust particles. Replacement shapes
+are not required to follow all attributes precisely, but will allow vanilla clients to receive some experience of your
+apps. One of the attributes that will definitely not be honored is the duration - particles will be send once
+per shape and last whatever they typically last in the game.
+
+Shapes will fail to draw and raise a runtime error if not all its required parameters
+are specified and all available shapes have some parameters that are required, so make sure to have them in place:
+
+On the client, shapes can recognize that they are being redrawn again with the same parameters, disregarding the
+duration parameter. This updates the expiry on the drawn shape to the new value, instead of adding new shape in its
+place. This can be used for toggling the shapes on and off that has been send previously with very large durations,
+or simply refresh the shapes periodically in more dynamic applications.
+
+Optional shared shape attributes:
+ * `color` - integer value indicating the main color of the shape in the form of red, green, blue and alpha components
+ in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`.
+ * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified
+ player, otherwise it will be send to all players in the dimension.
+
+Available shapes:
+ * `'line'` - draws a straight line between two points
+ * Required attributes:
+ * `from` - triple coordinates, entity, or block value indicating one end of the line
+ * `to` - other end of the line, same format as `from`
+ * Optional attributes:
+ * `width` - line thickness, defaults to 2.0pt
+
+ * `'box'` - draws a box with corners in specified points
+ * Required attributes:
+ * `from` - triple coordinates, entity, or block value indicating one corner of the box
+ * `to` - other corner, same format as `from`
+ * Optional attributes:
+ * `width` - mesh line thickness, defaults to 2.0pt
+ * `fill` - color for the box faces, defaults to no fill, use shared color attribute format
+ * `'sphere'` - draws a sphere
+ * Required attributes:
+ * `center` - center of the sphere
+ * `radius` - radius of the sphere
+ * Optional attributes:
+ * `level` - level of details, or grid size. The more the denser your sphere. Default level of 0, means that the
+ level of detail will be selected automatically based on radius.
+ * `width` - line thickness
+
### `create_marker(text, pos, rotation?, block?)`
Spawns a (permanent) marker entity with text or block at position. Returns that entity for further manipulations.
@@ -3302,6 +3360,9 @@ Returns current dimension that the script runs in.
Evaluates the expression `expr` with different dimension execution context. `smth` can be an entity,
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
+
+### `view_distance()`
+Returns the view distance of the server.
### `schedule(delay, function, args...)`
diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md
index 7d9da627..90860cc9 100644
--- a/docs/scarpet/api/Auxiliary.md
+++ b/docs/scarpet/api/Auxiliary.md
@@ -36,19 +36,54 @@ supplied, only that player will receive particles.
## Markers
-### `marker_box(from, to, duration?, color?, player?)`
+### `draw_shape(shape, duration, key?, value?, ... )`, `draw_shape(shape, duration, l(key?, value?, ... ))`, `draw_shape(shape, duration, attribute_map)`
-Draws a box with `from` and `to` corners in the box. The boxes will dissipate after `duration` ticks (defaults to 10).
-The color is an integer that indicate red, green, blue and alpha components in the form of `0xRRGGBBAA`, with the
-default of `-1`, so white opaque, or `0xFFFFFFFF`. If player is specified, only displays for that player, otherwise all
-players will see them. `marker_box` requires proper client synchronization to display these, and on non-supported
-clients, the box will be replaced with close matching dust particles, so using of `marker_box` is safe to use for
-scripts regardless who is targeted. The only consideration is that with vanilla clients, duration of particles cannot
-be controlled, and last whatever the original particles do.
+Draws a shape in the world that will expire in `duration` ticks. Other attributes of the shape should be provided as
+consecutive key - value argument pairs, either as next arguments, or packed in a list, or supplied as a proper key-value
+`map`. Arguments may include shared shape attributes, which are all optional, as well as shape-specific attributes, that
+could be either optional or required. Shapes will draw properly on all carpet clients. Other connected players that don't
+have carpet installed will still be able to see the required shapes in the form of dust particles. Replacement shapes
+are not required to follow all attributes precisely, but will allow vanilla clients to receive some experience of your
+apps. One of the attributes that will definitely not be honored is the duration - particles will be send once
+per shape and last whatever they typically last in the game.
-### `marker_line(from, to, duration?, color?, player?)`
+Shapes will fail to draw and raise a runtime error if not all its required parameters
+are specified and all available shapes have some parameters that are required, so make sure to have them in place:
-Draws a marker line on client's screen. Uses similar setup as `marker_box`.
+On the client, shapes can recognize that they are being redrawn again with the same parameters, disregarding the
+duration parameter. This updates the expiry on the drawn shape to the new value, instead of adding new shape in its
+place. This can be used for toggling the shapes on and off that has been send previously with very large durations,
+or simply refresh the shapes periodically in more dynamic applications.
+
+Optional shared shape attributes:
+ * `color` - integer value indicating the main color of the shape in the form of red, green, blue and alpha components
+ in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`.
+ * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified
+ player, otherwise it will be send to all players in the dimension.
+
+Available shapes:
+ * `'line'` - draws a straight line between two points
+ * Required attributes:
+ * `from` - triple coordinates, entity, or block value indicating one end of the line
+ * `to` - other end of the line, same format as `from`
+ * Optional attributes:
+ * `width` - line thickness, defaults to 2.0pt
+
+ * `'box'` - draws a box with corners in specified points
+ * Required attributes:
+ * `from` - triple coordinates, entity, or block value indicating one corner of the box
+ * `to` - other corner, same format as `from`
+ * Optional attributes:
+ * `width` - mesh line thickness, defaults to 2.0pt
+ * `fill` - color for the box faces, defaults to no fill, use shared color attribute format
+ * `'sphere'` - draws a sphere
+ * Required attributes:
+ * `center` - center of the sphere
+ * `radius` - radius of the sphere
+ * Optional attributes:
+ * `level` - level of details, or grid size. The more the denser your sphere. Default level of 0, means that the
+ level of detail will be selected automatically based on radius.
+ * `width` - line thickness
### `create_marker(text, pos, rotation?, block?)`
@@ -231,6 +266,9 @@ Returns current dimension that the script runs in.
Evaluates the expression `expr` with different dimension execution context. `smth` can be an entity,
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
+
+### `view_distance()`
+Returns the view distance of the server.
### `schedule(delay, function, args...)`
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index 39992450..6a616f30 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -48,6 +48,8 @@ __on_player_starts_sprinting(player)
__on_player_stops_sprinting(player)
__on_player_drops_item(player)
__on_player_drops_stack(player)
+__on_player_connects(player)
+__on_player_disconnects(player, reason)
__on_statistic(player, category, event, value) // player statistic changes
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 81803965..7e2a8d8a 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -17,6 +17,7 @@ current_dimension
day_time
destroy
diamond
+draw_shape
drop_item
emitted_light
entity_area
@@ -101,6 +102,7 @@ ticks_randomly
top
transparent
update
+view_distance
volume
without_updates
world_time
\ No newline at end of file
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index 4799c846..faf87463 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -30,4 +30,6 @@ __on_tick_nether
__on_player_takes_damage
__on_player_deals_damage
__on_player_dies
-__on_player_respawns
\ No newline at end of file
+__on_player_respawns
+__on_player_disconnects
+__on_player_connects
\ No newline at end of file
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index c17b3811..33416a05 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -68,9 +68,11 @@
format logger sound particle particle_line particle_rect
- run save tick_time world_time day_time last_tick_times game_tick seed current_dimension in_dimension
+ run save tick_time world_time day_time last_tick_times game_tick seed current_dimension in_dimension view_distance
- plop harvest destroy schedule nbt create_marker remove_all_markers load_app_data store_app_data escape_nbt statistic
+ plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
+
+ load_app_data store_app_data escape_nbt statistic
euler pi null true false
x y z p
@@ -110,6 +112,8 @@
__on_player_deals_damage
__on_player_dies
__on_player_respawns
+ __on_player_disconnects
+ __on_player_connects
$
00' 01\ 02' 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index ae2874cc..5b6ccf29 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -62,7 +62,7 @@ public class CarpetSettings
@Rule(
desc = "sets the language for carpet",
category = FEATURE,
- options = {"none", "zh_cn"},
+ options = {"none", "zh_cn", "zh_tw"},
strict = false,
validate = LanguageValidator.class
)
diff --git a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java
index ecd2fe61..62eeefff 100644
--- a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java
+++ b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java
@@ -9,6 +9,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import static carpet.script.CarpetEventServer.Event.PLAYER_CONNECTS;
+
@Mixin(PlayerManager.class)
public class PlayerManager_coreMixin
{
@@ -16,5 +18,6 @@ public class PlayerManager_coreMixin
private void onPlayerConnected(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci)
{
CarpetServer.onPlayerLoggedIn(player);
+ PLAYER_CONNECTS.onPlayerEvent(player);
}
}
diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_coreMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_coreMixin.java
index 1dfbc237..43fabd5c 100644
--- a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_coreMixin.java
+++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_coreMixin.java
@@ -13,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import static carpet.script.CarpetEventServer.Event.PLAYER_DISCONNECTS;
+
@Mixin(ServerPlayNetworkHandler.class)
public class ServerPlayNetworkHandler_coreMixin {
@Shadow
@@ -21,5 +23,6 @@ public class ServerPlayNetworkHandler_coreMixin {
@Inject(method = "onDisconnected", at = @At("HEAD"))
private void onPlayerDisconnect(Text reason, CallbackInfo ci) {
CarpetServer.onPlayerLoggedOut(this.player);
+ if (PLAYER_DISCONNECTS.isNeeded()) PLAYER_DISCONNECTS.onPlayerMessage(player, reason.asString());
}
}
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index f155040f..294bb08e 100644
--- a/src/main/java/carpet/network/ClientNetworkHandler.java
+++ b/src/main/java/carpet/network/ClientNetworkHandler.java
@@ -35,7 +35,7 @@ public class ClientNetworkHandler
}
});
put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false));
- put("renderShape", (p, t) -> {
+ put("scShape", (p, t) -> {
if (CarpetClient.shapes != null)
CarpetClient.shapes.addShape((CompoundTag)t);
});
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 200a02f5..aa26bbb5 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -128,7 +128,13 @@ public class CarpetEventServer
List argv = argumentSupplier.get(); // empty for onTickDone
ServerCommandSource source = cmdSourceSupplier.get();
assert argv.size() == reqArgs;
- callList.removeIf(call -> !CarpetServer.scriptServer.runas(source, call.host, call.function, argv)); // this actually does the calls
+ List fails = new ArrayList<>();
+ for (Callback call: callList)
+ {
+ if (!CarpetServer.scriptServer.runas(source, call.host, call.function, argv))
+ fails.add(call);
+ }
+ for (Callback call : fails) callList.remove(call);
}
}
public boolean addEventCall(ServerCommandSource source, String hostName, String funName, Function verifier)
@@ -549,6 +555,23 @@ public class CarpetEventServer
), player::getCommandSource);
}
},
+ PLAYER_CONNECTS("player_connects", 1, false) {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_DISCONNECTS("player_disconnects", 2, false) {
+ @Override
+ public void onPlayerMessage(ServerPlayerEntity player, String message)
+ {
+ handler.call( () -> Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new StringValue(message))
+ ), player::getCommandSource);
+ }
+ },
STATISTICS("statistic", 4, false)
{
private Identifier getStatId(Stat stat)
@@ -613,6 +636,7 @@ public class CarpetEventServer
public void onTick() { }
public void onChunkGenerated(ServerWorld world, Chunk chunk) { }
public void onPlayerEvent(ServerPlayerEntity player) { }
+ public void onPlayerMessage(ServerPlayerEntity player, String message) { }
public void onPlayerStatistic(ServerPlayerEntity player, Stat> stat, int amount) { }
public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { }
public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) { }
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 67395cb6..6090184d 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -2704,7 +2704,7 @@ public class CarpetExpression
ServerPlayerEntity player = null;
if (params.containsKey("player"))
{
- player = (ServerPlayerEntity) ((EntityValue)params.get("player")).getEntity();
+ player = (ServerPlayerEntity)((EntityValue)params.get("player")).getEntity();
}
ShapeDispatcher.sendShape(
(player==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player),
@@ -2987,6 +2987,12 @@ public class CarpetExpression
return (cc, tt) -> retval;
});
+ this.expr.addLazyFunction("view_distance", 0, (c, t, lv) -> {
+ ServerCommandSource s = ((CarpetContext)c).s;
+ Value retval = new NumericValue(s.getMinecraftServer().getPlayerManager().getViewDistance());
+ return (cc, tt) -> retval;
+ });
+
this.expr.addLazyFunction("in_dimension", 2, (c, t, lv) -> {
ServerCommandSource outerSource = ((CarpetContext)c).s;
Value dimensionValue = lv.get(0).evalValue(c);
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index bd2b669e..94f3b2fc 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -96,7 +96,7 @@ public class CarpetScriptHost extends ScriptHost
public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function)
{
super.addUserDefinedFunction(ctx, module, funName, function);
- // mcarpet
+ if (ctx.host.main != module) return; // not dealing with automatic imports / exports /configs / apps from imports
if (funName.startsWith("__")) // potential fishy activity
{
if (funName.startsWith("__on_")) // here we can make a determination if we want to only accept events from main module.
diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java
index 42364245..fca80136 100644
--- a/src/main/java/carpet/script/utils/ShapeDispatcher.java
+++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java
@@ -19,6 +19,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.command.arguments.ParticleArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.FloatTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
@@ -27,6 +28,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.dimension.DimensionType;
@@ -39,6 +41,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
+import java.util.Random;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -59,7 +62,7 @@ public class ShapeDispatcher
if (ServerNetworkHandler.validCarpetPlayers.contains(player))
{
if (tag == null) tag = ExpiringShape.toTag(params);
- ServerNetworkHandler.sendCustomCommand(player,"renderShape", tag);
+ ServerNetworkHandler.sendCustomCommand(player,"scShape", tag);
}
else
{
@@ -105,16 +108,14 @@ public class ShapeDispatcher
public static ExpiringShape create(CarpetContext cc, String shapeType, Map userParams)
{
userParams.put("shape", new StringValue(shapeType));
- Map validatedParams = new HashMap<>();
- userParams.forEach((key, value) ->
- {
+ userParams.keySet().forEach(key -> {
Param param = Param.coders.get(key);
if (param==null) throw new InternalExpressionException("Unknown feature for shape: "+key);
- validatedParams.put(key, param.validate(cc, value));
+ userParams.put(key, param.validate(cc, userParams.get(key)));
});
Function