diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index ad406637..4e0ea027 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -689,15 +689,22 @@ number('3bar') => null number('2')+number('2') => 4 -### `str(expr, params? ... ), str(expr, param_list)` +### `str(expr)`,`str(expr, params? ... )`, `str(expr, param_list)` -Returns a formatted string representing expression. Accepts formatting style accepted by `String.format`. -Supported types (with `"%?"` syntax): +If called with one argument, returns string representation of such value. + +Otherwise, returns a formatted string representing the expression. Arguments for formatting can either be provided as + each consecutive parameter, or as a list which then would be the only extra parameter. To format one list argument + , you can use `str(list)`, or `str('foo %s', l(list))`. + +Accepts formatting style accepted by `String.format`. +Supported types (with `"%"` syntax): * `d`, `o`, `x`: integers, octal, hex * `a`, `e`, `f`, `g`: floats * `b`: booleans * `s`: strings +* `%%`: '%' character
 str(null) => null
@@ -2069,13 +2076,17 @@ Returns spawn potential at a location (1.16+ only)
 Checks wordgen eligibility for a structure in a given chunk. If no structure is given, or `null`, then it will check
  for all structures. If bounding box of the structures is also requested, it will compute size of potential
   structures. This function, unlike other in the `structure*` category is not using world data nor accesses chunks
-  making it preferred for scoping ungenerated terrain.
+  making it preferred for scoping ungenerated terrain, but it takes some compute money to calculate the structure.
+  
+  Unlike `'structure'` this will return a tentative structure location. Random factors in world generation may prevent
+  actual structure from forming.
   
 If structure is specified, it will return `null` if a chunk is not eligible, `true` if the structure should appear, or 
-a pair of coordinates indicating bounding box of the structure.
+a map with two values: `'box'` for a pair of coordinates indicating bounding box of the structure, and `'pieces'` for 
+list of elements of the structure (as a tuple), with its name, direction, and box coordinates of the piece.
 
 If structure is not specified, it will return a set of structure names that are eligible, or a map with structures
-as keys, and pair of bounding box coordinates as values. An empty set or an empty map would indicate that nothing
+as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
 should be generated there.
 
 
@@ -2085,7 +2096,8 @@ Returns structure information for a given block position. Note that structure in
 blocks from the same chunk. `structures` function can be called with a block, or a block and a structure name. In 
 the first case it returns a map of structures at a given position, keyed by structure name, with values indicating 
 the bounding box of the structure - a pair of two 3-value coords (see examples). When called with an extra structure 
-name, returns list of components for that structure, with their name, direction and two sets of coordinates 
+name, returns a map with two values, `'box'` for bounding box of the structure, and `'pieces'` for a list of 
+components for that structure, with their name, direction and two sets of coordinates 
 indicating the bounding box of the structure piece.
 
 ### `structure_references(pos), structure_references(pos, structure_name)`
diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index 17145b86..e6ae9e39 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -532,13 +532,17 @@ Returns spawn potential at a location (1.16+ only)
 Checks wordgen eligibility for a structure in a given chunk. If no structure is given, or `null`, then it will check
  for all structures. If bounding box of the structures is also requested, it will compute size of potential
   structures. This function, unlike other in the `structure*` category is not using world data nor accesses chunks
-  making it preferred for scoping ungenerated terrain.
+  making it preferred for scoping ungenerated terrain, but it takes some compute money to calculate the structure.
+  
+  Unlike `'structure'` this will return a tentative structure location. Random factors in world generation may prevent
+  actual structure from forming.
   
 If structure is specified, it will return `null` if a chunk is not eligible, `true` if the structure should appear, or 
-a pair of coordinates indicating bounding box of the structure.
+a map with two values: `'box'` for a pair of coordinates indicating bounding box of the structure, and `'pieces'` for 
+list of elements of the structure (as a tuple), with its name, direction, and box coordinates of the piece.
 
 If structure is not specified, it will return a set of structure names that are eligible, or a map with structures
-as keys, and pair of bounding box coordinates as values. An empty set or an empty map would indicate that nothing
+as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
 should be generated there.
 
 
@@ -548,7 +552,8 @@ Returns structure information for a given block position. Note that structure in
 blocks from the same chunk. `structures` function can be called with a block, or a block and a structure name. In 
 the first case it returns a map of structures at a given position, keyed by structure name, with values indicating 
 the bounding box of the structure - a pair of two 3-value coords (see examples). When called with an extra structure 
-name, returns list of components for that structure, with their name, direction and two sets of coordinates 
+name, returns a map with two values, `'box'` for bounding box of the structure, and `'pieces'` for a list of 
+components for that structure, with their name, direction and two sets of coordinates 
 indicating the bounding box of the structure piece.
 
 ### `structure_references(pos), structure_references(pos, structure_name)`
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index 44a673f9..41a00df4 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -583,4 +583,10 @@ public class CarpetSettings
             category = CLIENT
     )
     public static boolean fogOff = false;
+
+    @Rule(
+            desc = "Creative No Clip",
+            category = {CREATIVE, CLIENT}
+    )
+    public static boolean creativeNoClip = false;
 }
diff --git a/src/main/java/carpet/helpers/FeatureGenerator.java b/src/main/java/carpet/helpers/FeatureGenerator.java
index 98f47007..41228a40 100644
--- a/src/main/java/carpet/helpers/FeatureGenerator.java
+++ b/src/main/java/carpet/helpers/FeatureGenerator.java
@@ -154,10 +154,9 @@ public class FeatureGenerator
         return null;
     }
 
-    public static BlockBox DEFAULT_BOX = new BlockBox(0,0,0,1,1,1);
-    public static BlockBox shouldStructureStartAt(ServerWorld world, BlockPos pos, StructureFeature structure, boolean computeBox)
+    public static StructureStart shouldStructureStartAt(ServerWorld world, BlockPos pos, StructureFeature structure, boolean computeBox)
     {
-        ChunkGenerator generator = world.getChunkManager().getChunkGenerator();
+        ChunkGenerator generator = world.getChunkManager().getChunkGenerator();
         if (!generator.getBiomeSource().hasStructureFeature(structure))
             return null;
         BiomeAccess biomeAccess = world.getBiomeAccess().withSource(generator.getBiomeSource());
@@ -166,12 +165,12 @@ public class FeatureGenerator
         Biome biome = biomeAccess.getBiome(new BlockPos(chunkPos.getStartX() + 9, 0, chunkPos.getStartZ() + 9));
         if (structure.method_27217(biomeAccess, generator, chunkRandom, chunkPos.x, chunkPos.z, biome)) // should start at
         {
-            if (!computeBox) return DEFAULT_BOX;
+            if (!computeBox) return StructureStart.DEFAULT;
             StructureManager manager = world.getStructureManager();
             StructureStart structureStart3 = structure.getStructureStartFactory().create(structure, chunkPos.x, chunkPos.z, BlockBox.empty(), 0, generator.getSeed());
             structureStart3.init(generator, manager, chunkPos.x, chunkPos.z, biome);
             if (!structureStart3.hasChildren()) return null;
-            return structureStart3.getBoundingBox();
+            return structureStart3;
         }
         return null;
     }
@@ -196,9 +195,9 @@ public class FeatureGenerator
         structureToFeature.put(Feature.MINESHAFT.getName(), Arrays.asList("mineshaft", "mineshaft_mesa"));
         structureToFeature.put(Feature.IGLOO.getName(), Collections.singletonList("igloo"));
         structureToFeature.put(Feature.NETHER_BRIDGE.getName(), Collections.singletonList("fortress"));
-        structureToFeature.put("Nether_Fossil", Collections.singletonList("nether_fossil"));
-        structureToFeature.put("Bastion_Remnant", Arrays.asList("bastion_remnant", "bastion_remnant_housing", "bastion_remnant_stable", "bastion_remnant_treasure", "bastion_remnant_bridge"));
-        structureToFeature.put("Ruined_Portal", Collections.singletonList("ruined_portal"));
+        structureToFeature.put(Feature.NETHER_FOSSIL.getName(), Collections.singletonList("nether_fossil"));
+        structureToFeature.put(Feature.BASTION_REMNANT.getName(), Arrays.asList("bastion_remnant", "bastion_remnant_housing", "bastion_remnant_stable", "bastion_remnant_treasure", "bastion_remnant_bridge"));
+        structureToFeature.put(Feature.RUINED_PORTAL.getName(), Collections.singletonList("ruined_portal"));
 
         structureToFeature.forEach((key, value) -> value.forEach(el -> featureToStructure.put(el, key)));
     }
diff --git a/src/main/java/carpet/mixins/PistonBlockEntity_creativeNoClipMixin.java b/src/main/java/carpet/mixins/PistonBlockEntity_creativeNoClipMixin.java
new file mode 100644
index 00000000..9e81659f
--- /dev/null
+++ b/src/main/java/carpet/mixins/PistonBlockEntity_creativeNoClipMixin.java
@@ -0,0 +1,29 @@
+package carpet.mixins;
+
+import carpet.CarpetSettings;
+import net.minecraft.block.entity.PistonBlockEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.math.Direction;
+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.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(PistonBlockEntity.class)
+public class PistonBlockEntity_creativeNoClipMixin
+{
+    @Inject(method = "method_23672", at = @At("HEAD"), cancellable = true)
+    private static void dontPushSpectators(Direction direction, Entity entity, double d, Direction direction2, CallbackInfo ci)
+    {
+        if (CarpetSettings.creativeNoClip && entity instanceof PlayerEntity && (((PlayerEntity) entity).isCreative()) && ((PlayerEntity) entity).abilities.flying) ci.cancel();
+    }
+
+    @Redirect(method = "pushEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;setVelocity(DDD)V"))
+    private void ignoreAccel(Entity entity, double x, double y, double z)
+    {
+        if (CarpetSettings.creativeNoClip && entity instanceof PlayerEntity && (((PlayerEntity) entity).isCreative()) && ((PlayerEntity) entity).abilities.flying) return;
+        entity.setVelocity(x,y,z);
+    }
+}
diff --git a/src/main/java/carpet/mixins/PlayerEntity_creativeNoClipMixin.java b/src/main/java/carpet/mixins/PlayerEntity_creativeNoClipMixin.java
new file mode 100644
index 00000000..ea50dab4
--- /dev/null
+++ b/src/main/java/carpet/mixins/PlayerEntity_creativeNoClipMixin.java
@@ -0,0 +1,56 @@
+package carpet.mixins;
+
+import carpet.CarpetSettings;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.MovementType;
+import net.minecraft.entity.player.PlayerAbilities;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(PlayerEntity.class)
+public abstract class PlayerEntity_creativeNoClipMixin extends LivingEntity
+{
+    @Shadow public abstract boolean isCreative();
+
+    @Shadow @Final public PlayerAbilities abilities;
+
+    protected PlayerEntity_creativeNoClipMixin(EntityType type, World world)
+    {
+        super(type, world);
+    }
+
+    @Redirect(method = "tick", at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
+    )
+    private boolean canClipTroughWorld(PlayerEntity playerEntity)
+    {
+        return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
+
+    }
+
+    @Redirect(method = "tickMovement", at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
+    )
+    private boolean collidesWithEntities(PlayerEntity playerEntity)
+    {
+        return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
+    }
+
+    @Redirect(method = "updateSize", at = @At(
+            value = "INVOKE",
+            target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
+    )
+    private boolean spectatorsDontPose(PlayerEntity playerEntity)
+    {
+        return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
+    }
+}
diff --git a/src/main/java/carpet/mixins/WorldRenderer_creativeNoClipMixin.java b/src/main/java/carpet/mixins/WorldRenderer_creativeNoClipMixin.java
new file mode 100644
index 00000000..4e8a8109
--- /dev/null
+++ b/src/main/java/carpet/mixins/WorldRenderer_creativeNoClipMixin.java
@@ -0,0 +1,19 @@
+package carpet.mixins;
+
+import carpet.CarpetSettings;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.render.WorldRenderer;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(WorldRenderer.class)
+public class WorldRenderer_creativeNoClipMixin
+{
+    @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isSpectator()Z"))
+    private boolean canSeeWorld(ClientPlayerEntity clientPlayerEntity)
+    {
+        return clientPlayerEntity.isSpectator() || (CarpetSettings.creativeNoClip && clientPlayerEntity.isCreative());
+    }
+
+}
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 989cc0f5..0a8cf83b 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -398,6 +398,32 @@ public class CarpetExpression
         }
     }
 
+
+
+    private static Value structureToValue(StructureStart structure)
+    {
+        if (structure == null || structure == StructureStart.DEFAULT) return Value.NULL;
+        List pieces = new ArrayList<>();
+        for (StructurePiece piece : structure.getChildren())
+        {
+            BlockBox box = piece.getBoundingBox();
+            pieces.add(ListValue.of(
+                    new StringValue( NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_PIECE.getId(piece.getType()))),
+                    (piece.getFacing()== null)?Value.NULL: new StringValue(piece.getFacing().getName()),
+                    ListValue.fromTriple(box.minX, box.minY, box.minZ),
+                    ListValue.fromTriple(box.maxX, box.maxY, box.maxZ)
+            ));
+        };
+        BlockBox boundingBox = structure.getBoundingBox();
+        Map ret = new HashMap<>();
+        ret.put(new StringValue("box"), ListValue.of(
+                ListValue.fromTriple(boundingBox.minX, boundingBox.minY, boundingBox.minZ),
+                ListValue.fromTriple(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ)
+        ));
+        ret.put(new StringValue("pieces"), ListValue.wrap(pieces));
+        return MapValue.wrap(ret);
+    }
+
     private void API_BlockManipulation()
     {
         this.expr.addLazyFunction("block", -1, (c, t, lv) ->
@@ -1148,25 +1174,20 @@ public class CarpetExpression
             }
             if (structure != null)
             {
-                BlockBox box = FeatureGenerator.shouldStructureStartAt(world, pos, structure, needSize);
-                if (box == null) return LazyValue.NULL;
+                StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, structure, needSize);
+                if (start == null) return LazyValue.NULL;
                 if (!needSize) return LazyValue.TRUE;
-                Value ret = ListValue.of(
-                        ListValue.of(new NumericValue(box.minX), new NumericValue(box.minY), new NumericValue(box.minZ)),
-                        ListValue.of(new NumericValue(box.maxX), new NumericValue(box.maxY), new NumericValue(box.maxZ))
-                );
+                Value ret = structureToValue(start);
                 return (_c, _t) -> ret;
             }
             Map ret = new HashMap<>();
             for(StructureFeature str : Feature.STRUCTURES.values())
             {
-                BlockBox box = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize);
-                if (box == null) continue;
+                StructureStart start = FeatureGenerator.shouldStructureStartAt(world, pos, str, needSize);
+                if (start == null) continue;
+
                 Value key = new StringValue(FeatureGenerator.structureToFeature.get(str.getName()).get(0));
-                ret.put(key, (!needSize)?Value.NULL:ListValue.of(
-                        ListValue.of(new NumericValue(box.minX), new NumericValue(box.minY), new NumericValue(box.minZ)),
-                        ListValue.of(new NumericValue(box.maxX), new NumericValue(box.maxY), new NumericValue(box.maxZ))
-                ));
+                ret.put(key, (!needSize)?Value.NULL:structureToValue(start));
             }
             Value retMap = MapValue.wrap(ret);
             return (_c, _t) -> retMap;
@@ -1188,28 +1209,17 @@ public class CarpetExpression
                     if (start == StructureStart.DEFAULT)
                         continue;
                     BlockBox box = start.getBoundingBox();
-                    ListValue coord1 = ListValue.of(new NumericValue(box.minX), new NumericValue(box.minY), new NumericValue(box.minZ));
-                    ListValue coord2 = ListValue.of(new NumericValue(box.maxX), new NumericValue(box.maxY), new NumericValue(box.maxZ));
-                    structureList.put(new StringValue(FeatureGenerator.structureToFeature.get(entry.getKey()).get(0)), ListValue.of(coord1, coord2));
+                    structureList.put(
+                            new StringValue(FeatureGenerator.structureToFeature.get(entry.getKey()).get(0)),
+                            ListValue.of(ListValue.fromTriple(box.minX, box.minY, box.minZ), ListValue.fromTriple(box.maxX, box.maxY, box.maxZ))
+                    );
                 }
                 Value ret = MapValue.wrap(structureList);
                 return (_c, _t) -> ret;
             }
             String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT);
             StructureStart start = structures.get(FeatureGenerator.featureToStructure.get(structureName));
-            if (start == null || start == StructureStart.DEFAULT) return LazyValue.NULL;
-            List pieces = new ArrayList<>();
-            for (StructurePiece piece : start.getChildren())
-            {
-                BlockBox box = piece.getBoundingBox();
-                pieces.add(ListValue.of(
-                        new StringValue( NBTSerializableValue.nameFromRegistryId(Registry.STRUCTURE_PIECE.getId(piece.getType()))),
-                        (piece.getFacing()== null)?Value.NULL: new StringValue(piece.getFacing().getName()),
-                        ListValue.of(new NumericValue(box.minX), new NumericValue(box.minY), new NumericValue(box.minZ)),
-                        ListValue.of(new NumericValue(box.maxX), new NumericValue(box.maxY), new NumericValue(box.maxZ))
-                ));
-            }
-            Value ret = ListValue.wrap(pieces);
+            Value ret = structureToValue(start);
             return (_c, _t) -> ret;
         });
 
diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java
index 2d1ac5dc..e20d32b3 100644
--- a/src/main/java/carpet/script/value/ListValue.java
+++ b/src/main/java/carpet/script/value/ListValue.java
@@ -75,6 +75,18 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
         );
     }
 
+    public static Value fromTriple(double a, double b, double c)
+    {
+        return ListValue.of(new NumericValue(a), new NumericValue(b), new NumericValue(c));
+    }
+
+    public static Value fromTriple(int a, int b, int c)
+    {
+        return fromTriple((double) a, b, c);
+    }
+
+
+
 
     public static ListValue wrap(List list)
     {
diff --git a/src/main/resources/assets/carpet/scripts/overlay.sc b/src/main/resources/assets/carpet/scripts/overlay.sc
index 6868fb91..0b501051 100644
--- a/src/main/resources/assets/carpet/scripts/overlay.sc
+++ b/src/main/resources/assets/carpet/scripts/overlay.sc
@@ -93,7 +93,7 @@ __structure_renderer(player_name) ->
 		for (starts, l(position, name) = _;
 		    structure_data = structures(position):name;
 		    if (!structure_data, continue()); // messed up references - shouldn't happen
-		    structure_pieces = structures(position, name);
+		    structure_pieces = structures(position, name):'pieces';
 		    if (!structure_pieces, continue()); // messed up references - shouldn't happen
 		    l(from, to) = structure_data;
 		    total_size = _euclidean(from, to);
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index 6fd71f94..83bfb498 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -157,7 +157,10 @@
     "PistonBlockEntity_movableTEMixin",
     "World_movableTEMixin",
     "WorldChunk_movableTEMixin",
-    "PistonHandler_movableTEMixin"
+    "PistonHandler_movableTEMixin",
+    "PlayerEntity_creativeNoClipMixin",
+    "PistonBlockEntity_creativeNoClipMixin"
+
   ],
   "client": [
     "RenderTickCounter_tickSpeedMixin",
@@ -170,7 +173,9 @@
     "PistonBlockEntityRenderer_movableTEMixin",
     "ClientPlayNetworkHandler_smoothClientAnimationsMixin",
     "ClientPlayerEntity_clientCommandMixin",
-    "WorldRenderer_fogOffMixin"
+    "WorldRenderer_fogOffMixin",
+    "WorldRenderer_creativeNoClipMixin"
+
   ],
   "server": [
     "MinecraftDedicatedServerMixin"