diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 17b9f7b9..b468f374 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -674,7 +674,7 @@ Returns the deep copy of the expression. Can be used to copy mutable objects, li Returns the string value indicating type of the expression. Possible outcomes are `null`, `number`, `string`, `list`, `map`, `iterator`, `function`, `task`, -as well as minecraft related concepts like `block`, `entity`, `nbt`, `text` +as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` @@ -3371,11 +3371,32 @@ Optional shared shape attributes: instance makes so that the shape follows the entity, but stays at the same, absolute Y coordinate. Available shapes: - * `'line'` - draws a straight line between two points + * `'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` + * `'label'` - draws a text in the world. Default `line` attribute controls main font color. + `fill` controls the color of the background. + * Required attributes: + * `pos` - position + * `text` - string or formatted text to display + * Optional attributes + * `value` - string or formatted text to display instead of the main `text`. `value` unlike `text` + is not used to determine uniqueness of the drawn text so can be used to + display smoothly dynamic elements where value of an element is constantly + changing and updates to it are being sent from the server. + * `size` - float. Default font size is 10. + * `facing` - text direction, where its facing. Possible options are: `player` (default, text + always rotates to face the player), `north`, `south`, `east`, `west`, `up`, `down` + * `align` - text alignment with regards to `pos`. Default is `center` (displayed text is + centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos` + indicates the end of text). + * `tilt` - additional rotation of the text on the canvas + * `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`) + with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which + can be used to display multiple lines of text bound to the same `pos` + * `'box'` - draws a box with corners in specified points * Required attributes: * `from` - triple coordinates, entity, or block value indicating one corner of the box diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index d471e769..2d3d88c4 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -78,11 +78,32 @@ Optional shared shape attributes: instance makes so that the shape follows the entity, but stays at the same, absolute Y coordinate. Available shapes: - * `'line'` - draws a straight line between two points + * `'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` + * `'label'` - draws a text in the world. Default `line` attribute controls main font color. + `fill` controls the color of the background. + * Required attributes: + * `pos` - position + * `text` - string or formatted text to display + * Optional attributes + * `value` - string or formatted text to display instead of the main `text`. `value` unlike `text` + is not used to determine uniqueness of the drawn text so can be used to + display smoothly dynamic elements where value of an element is constantly + changing and updates to it are being sent from the server. + * `size` - float. Default font size is 10. + * `facing` - text direction, where its facing. Possible options are: `player` (default, text + always rotates to face the player), `north`, `south`, `east`, `west`, `up`, `down` + * `align` - text alignment with regards to `pos`. Default is `center` (displayed text is + centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos` + indicates the end of text). + * `tilt` - additional rotation of the text on the canvas + * `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`) + with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which + can be used to display multiple lines of text bound to the same `pos` + * `'box'` - draws a box with corners in specified points * Required attributes: * `from` - triple coordinates, entity, or block value indicating one corner of the box diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 504d20a9..9a23faa3 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -10,7 +10,7 @@ Returns the deep copy of the expression. Can be used to copy mutable objects, li Returns the string value indicating type of the expression. Possible outcomes are `null`, `number`, `string`, `list`, `map`, `iterator`, `function`, `task`, -as well as minecraft related concepts like `block`, `entity`, `nbt`, `text` +as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index cebcb143..2d3aa29d 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -7,6 +7,7 @@ import carpet.script.Expression; import carpet.script.exception.InternalExpressionException; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; +import carpet.script.value.FormattedTextValue; import carpet.script.value.ListValue; import carpet.script.value.MapValue; import carpet.script.value.NumericValue; @@ -233,6 +234,7 @@ public class ShapeDispatcher put("box", creator(Box::new)); put("sphere", creator(Sphere::new)); put("cylinder", creator(Cylinder::new)); + put("label", creator(Text::new)); }}; private static Function,ExpiringShape> creator(Supplier shapeFactory) { @@ -389,6 +391,117 @@ public class ShapeDispatcher } } + public static class Text extends ExpiringShape + { + private final Set required = ImmutableSet.of("pos", "text"); + private final Map optional = ImmutableMap.builder(). + put("facing", new StringValue("player")). + put("raise", new NumericValue(0)). + put("tilt", new NumericValue(0)). + put("indent", new NumericValue(0)). + put("height", new NumericValue(0)). + put("align", new StringValue("center")). + put("size", new NumericValue(10)). + put("value", Value.NULL). + build(); + @Override + protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } + @Override + protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } + public Text() { } + + Vec3d pos; + String text; + int textcolor; + int textbck; + + Direction facing; + float raise; + float tilt; + float size; + float indent; + int align; + float height; + String value; + + @Override + protected void init(Map options) + { + super.init(options); + pos = vecFromValue(options.get("pos")); + text = options.get("text").getString(); + value = text; + if (options.containsKey("value")) + { + value = options.get("value").getString(); + } + textcolor = rgba2argb(color); + textbck = rgba2argb(fillColor); + String dir = options.getOrDefault("facing", optional.get("facing")).getString(); + facing = null; + switch (dir) + { + case "north": facing = Direction.NORTH; break; + case "south": facing = Direction.SOUTH; break; + case "east": facing = Direction.EAST; break; + case "west": facing = Direction.WEST; break; + case "up": facing = Direction.UP; break; + case "down": facing = Direction.DOWN; break; + } + align = 0; + if (options.containsKey("align")) + { + String alignStr = options.get("align").getString(); + if ("right".equalsIgnoreCase(alignStr)) + align = 1; + else + align = -1; + } + + raise = NumericValue.asNumber(options.getOrDefault("raise", optional.get("raise"))).getFloat(); + tilt = NumericValue.asNumber(options.getOrDefault("tilt", optional.get("tilt"))).getFloat(); + indent = NumericValue.asNumber(options.getOrDefault("indent", optional.get("indent"))).getFloat(); + height = NumericValue.asNumber(options.getOrDefault("height", optional.get("height"))).getFloat(); + + size = NumericValue.asNumber(options.getOrDefault("size", optional.get("size"))).getFloat(); + } + + private int rgba2argb(int color) + { + int r = Math.max(1, color >> 24 & 0xFF); + int g = Math.max(1, color >> 16 & 0xFF); + int b = Math.max(1, color >> 8 & 0xFF); + int a = color & 0xFF; + return (a << 24) + (r << 16) + (g << 8) + b; + } + + + @Override + public Consumer alternative() + { + return s -> {}; + } + + @Override + public long calcKey() + { + long hash = super.calcKey(); + hash ^= 5; hash *= 1099511628211L; + hash ^= pos.hashCode(); hash *= 1099511628211L; + hash ^= text.hashCode(); hash *= 1099511628211L; + if (facing!= null) hash ^= facing.hashCode(); hash *= 1099511628211L; + hash ^= Float.hashCode(raise); hash *= 1099511628211L; + hash ^= Float.hashCode(tilt); hash *= 1099511628211L; + hash ^= Float.hashCode(indent); hash *= 1099511628211L; + hash ^= Float.hashCode(height); hash *= 1099511628211L; + hash ^= Float.hashCode(size); hash *= 1099511628211L; + hash ^= Integer.hashCode(align); hash *= 1099511628211L; + + return hash; + } + } + + public static class Box extends ExpiringShape { private final Set required = ImmutableSet.of("from", "to"); @@ -689,7 +802,7 @@ public class ShapeDispatcher public long calcKey() { long hash = super.calcKey(); - hash ^= 3; hash *= 1099511628211L; + hash ^= 4; hash *= 1099511628211L; hash ^= center.hashCode(); hash *= 1099511628211L; hash ^= Double.hashCode(radius); hash *= 1099511628211L; hash ^= Double.hashCode(height); hash *= 1099511628211L; @@ -714,11 +827,22 @@ public class ShapeDispatcher put("from", new Vec3Param("from", false)); put("to", new Vec3Param("to", true)); put("center", new Vec3Param("center", false)); + put("pos", new Vec3Param("pos", false)); put("radius", new PositiveFloatParam("radius")); put("level", new PositiveIntParam("level")); put("height", new FloatParam("height")); put("axis", new StringChoiceParam("axis", "x", "y", "z")); put("points", new PointsParam("points")); + put("text", new FormattedTextParam("text")); + put("value", new FormattedTextParam("value")); + put("size", new PositiveIntParam("size")); + put("align", new StringChoiceParam("align", "center", "left", "right")); + + put("indent", new FloatParam("indent")); + put("raise", new FloatParam("raise")); + put("tilt", new FloatParam("tilt")); + put("facing", new StringChoiceParam("axis", "player", "north", "south", "east", "west", "up", "down")); + }}; protected String id; protected Param(String id) @@ -739,6 +863,34 @@ public class ShapeDispatcher public Tag toTag(Value value) { return StringTag.of(value.getString()); } public Value decode(Tag tag) { return new StringValue(tag.asString()); } } + public static class TextParam extends StringParam + { + protected TextParam(String id) + { + super(id); + } + @Override + public Value validate(Map options, CarpetContext cc, Value value) + { + return value; + } + } + public static class FormattedTextParam extends StringParam + { + + protected FormattedTextParam(String id) + { + super(id); + } + @Override + public Value validate(Map options, CarpetContext cc, Value value) + { + if (value instanceof FormattedTextValue) + return new StringValue(((FormattedTextValue) value).getText().asFormattedString()); + return value; + } + } + public static class StringChoiceParam extends StringParam { diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java index 78020763..9382c269 100644 --- a/src/main/java/carpet/script/utils/ShapesRenderer.java +++ b/src/main/java/carpet/script/utils/ShapesRenderer.java @@ -4,10 +4,14 @@ import carpet.CarpetSettings; import com.mojang.blaze3d.systems.RenderSystem; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; import net.minecraft.client.render.BufferBuilder; import net.minecraft.client.render.Camera; import net.minecraft.client.render.Tessellator; +import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.util.math.Matrix4f; +import net.minecraft.client.util.math.Rotation3; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.util.math.Direction; @@ -33,6 +37,7 @@ public class ShapesRenderer put("box", RenderedBox::new); put("sphere", RenderedSphere::new); put("cylinder", RenderedCylinder::new); + put("label", RenderedText::new); }}; public ShapesRenderer(MinecraftClient minecraftClient) @@ -60,6 +65,8 @@ public class ShapesRenderer RenderSystem.disableCull(); RenderSystem.disableLighting(); RenderSystem.depthMask(false); + // causes water to vanish + //RenderSystem.depthMask(true); //RenderSystem.polygonOffset(-3f, -3f); //RenderSystem.enablePolygonOffset(); //Entity entity = this.client.gameRenderer.getCamera().getFocusedEntity(); @@ -181,6 +188,80 @@ public class ShapesRenderer } } + public static class RenderedText extends RenderedShape + { + + protected RenderedText(MinecraftClient client, ShapeDispatcher.ExpiringShape shape) + { + super(client, (ShapeDispatcher.Text)shape); + } + + @Override + public void renderLines(Tessellator tessellator, BufferBuilder builder, double cx, double cy, double cz, float partialTick) + { + if (shape.a == 0.0) return; + Vec3d v1 = shape.relativiseRender(client.world, shape.pos, partialTick); + Camera camera1 = client.gameRenderer.getCamera(); + TextRenderer textRenderer = client.textRenderer; + double d = camera1.getPos().x; + double e = camera1.getPos().y; + double f = camera1.getPos().z; + RenderSystem.pushMatrix(); + RenderSystem.translatef((float)(v1.x - d), (float)(v1.y - e), (float)(v1.z - f)); + RenderSystem.normal3f(0.0F, 1.0F, 0.0F); + + if (shape.facing == null) + { + RenderSystem.multMatrix(new Matrix4f(camera1.getRotation())); + } + else + { + switch (shape.facing) + { + case NORTH: + break; + case SOUTH: + RenderSystem.rotatef(180.0f, 0.0f, 1.0f, 0.0f); + break; + case EAST: + RenderSystem.rotatef(270.0f, 0.0f, 1.0f, 0.0f); + break; + case WEST: + RenderSystem.rotatef(90.0f, 0.0f, 1.0f, 0.0f); + break; + case UP: + RenderSystem.rotatef(90.0f, 1.0f, 0.0f, 0.0f); + break; + case DOWN: + RenderSystem.rotatef(-90.0f, 1.0f, 0.0f, 0.0f); + break; + } + } + RenderSystem.scalef(shape.size* 0.0025f, -shape.size*0.0025f, shape.size*0.0025f); + if (shape.tilt!=0.0f) + { + RenderSystem.rotatef(shape.tilt, 0.0f, 0.0f, 1.0f); + } + RenderSystem.translatef(-10*shape.indent, -10*shape.height, (float) (-10*renderEpsilon)-10*shape.raise); + //if (visibleThroughWalls) RenderSystem.disableDepthTest(); + RenderSystem.scalef(-1.0F, 1.0F, 1.0F); + + float text_x = 0; + if (shape.align == 0) + { + text_x = (float)(-textRenderer.getStringWidth(shape.value)) / 2.0F; + } + else if (shape.align == 1) + { + text_x = (float)(-textRenderer.getStringWidth(shape.value)); + } + VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer()); + textRenderer.draw(shape.value, text_x, 0.0F, shape.textcolor, false, Rotation3.identity().getMatrix(), immediate, false, shape.textbck, 15728880); + immediate.draw(); + RenderSystem.popMatrix(); + } + } + public static class RenderedBox extends RenderedShape { diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index 68983085..218575ca 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -7,8 +7,8 @@ import net.minecraft.text.Text; public class FormattedTextValue extends StringValue { - BaseText text; - public FormattedTextValue(BaseText text) + Text text; + public FormattedTextValue(Text text) { super(null); this.text = text; @@ -36,7 +36,7 @@ public class FormattedTextValue extends StringValue return "text"; } - public BaseText getText() + public Text getText() { return text; } @@ -47,4 +47,14 @@ public class FormattedTextValue extends StringValue if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this); return StringTag.of(Text.Serializer.toJson(text)); } + + public String serialize() + { + return Text.Serializer.toJson(text); + } + public static FormattedTextValue deserialize(String serialized) + { + return new FormattedTextValue(Text.Serializer.fromJson(serialized)); + } + }