diff --git a/src/main/java/carpet/helpers/FeatureGenerator.java b/src/main/java/carpet/helpers/FeatureGenerator.java index 8b02f469..6c5b8e9e 100644 --- a/src/main/java/carpet/helpers/FeatureGenerator.java +++ b/src/main/java/carpet/helpers/FeatureGenerator.java @@ -148,11 +148,11 @@ public class FeatureGenerator { structureToFeature.put("Monument", Collections.singletonList("monument")); structureToFeature.put("EndCity", Collections.singletonList("end_city")); - structureToFeature.put("Ocean_Ruin", Arrays.asList("oceanruin", "oceanruin_warm", "oceanruin_small", "oceanruin_warm_small", "oceanruin_tall", "oceanruin_warm_tall")); + structureToFeature.put("Ocean_Ruin", Arrays.asList("ocean_ruin", "ocean_ruin_warm", "ocean_ruin_small", "ocean_ruin_warm_small", "ocean_ruin_tall", "ocean_ruin_warm_tall")); structureToFeature.put("Village", Arrays.asList("village", "village_desert", "village_savanna", "village_taiga", "village_snowy")); structureToFeature.put("Mansion", Collections.singletonList("mansion")); structureToFeature.put("Buried_Treasure", Collections.singletonList("treasure")); - structureToFeature.put("Swamp_Hut", Collections.singletonList("witchhut")); + structureToFeature.put("Swamp_Hut", Collections.singletonList("witch_hut")); structureToFeature.put("Stronghold", Collections.singletonList("stronghold")); structureToFeature.put("Desert_Pyramid", Collections.singletonList("desert_temple")); structureToFeature.put("Jungle_Pyramid", Collections.singletonList("jungle_temple")); @@ -175,20 +175,20 @@ public class FeatureGenerator put("igloo", ((StructureFeatureInterface)Feature.IGLOO)::gridAnywhere); put("shipwreck", gridCustomStructure(Feature.SHIPWRECK, new ShipwreckFeatureConfig(true), Biomes.PLAINS)); put("shipwreck2", gridCustomStructure(Feature.SHIPWRECK, new ShipwreckFeatureConfig(false), Biomes.PLAINS)); - put("witchhut", ((StructureFeatureInterface)Feature.SWAMP_HUT)::gridAnywhere); + put("witch_hut", ((StructureFeatureInterface)Feature.SWAMP_HUT)::gridAnywhere); put("stronghold", ((StructureFeatureInterface)Feature.STRONGHOLD)::gridAnywhere); - put("oceanruin_small", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_small", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 0.0F, 0.5F), Biomes.PLAINS)); - put("oceanruin_warm_small", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm_small", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 0.0F, 0.5F), Biomes.PLAINS)); - put("oceanruin_tall", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_tall", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 1.0F, 0.0F), Biomes.PLAINS)); - put("oceanruin_warm_tall", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm_tall", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 1.0F, 0.0F), Biomes.PLAINS)); - put("oceanruin", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 1.0F, 1.0F), Biomes.PLAINS)); - put("oceanruin_warm", gridCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm", gridCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 1.0F, 1.0F), Biomes.PLAINS)); put("treasure", ((StructureFeatureInterface)Feature.BURIED_TREASURE)::gridAnywhere); @@ -291,20 +291,20 @@ public class FeatureGenerator put("igloo", ((StructureFeatureInterface)Feature.IGLOO)::plopAnywhere); put("shipwreck", spawnCustomStructure(Feature.SHIPWRECK, new ShipwreckFeatureConfig(true), Biomes.PLAINS)); put("shipwreck2", spawnCustomStructure(Feature.SHIPWRECK, new ShipwreckFeatureConfig(false), Biomes.PLAINS)); - put("witchhut", ((StructureFeatureInterface)Feature.SWAMP_HUT)::plopAnywhere); + put("witch_hut", ((StructureFeatureInterface)Feature.SWAMP_HUT)::plopAnywhere); put("stronghold", ((StructureFeatureInterface)Feature.STRONGHOLD)::plopAnywhere); - put("oceanruin_small", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_small", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 0.0F, 0.5F), Biomes.PLAINS)); - put("oceanruin_warm_small", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm_small", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 0.0F, 0.5F), Biomes.PLAINS)); - put("oceanruin_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_tall", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 1.0F, 0.0F), Biomes.PLAINS)); - put("oceanruin_warm_tall", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm_tall", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 1.0F, 0.0F), Biomes.PLAINS)); - put("oceanruin", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.COLD, 1.0F, 1.0F), Biomes.PLAINS)); - put("oceanruin_warm", spawnCustomStructure(Feature.OCEAN_RUIN, + put("ocean_ruin_warm", spawnCustomStructure(Feature.OCEAN_RUIN, new OceanRuinFeatureConfig(OceanRuinFeature.BiomeType.WARM, 1.0F, 1.0F), Biomes.PLAINS)); put("treasure", ((StructureFeatureInterface)Feature.BURIED_TREASURE)::plopAnywhere); diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index f97f3f96..9783a422 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -103,6 +103,7 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.ChunkRandom; import net.minecraft.loot.context.LootContext; import net.minecraft.loot.context.LootContextParameters; import org.apache.commons.lang3.tuple.Pair; @@ -608,6 +609,8 @@ public class CarpetExpression *

Numeric function, indicating hardness of a block.

*

blast_resistance(pos)

*

Numeric function, indicating blast_resistance of a block.

+ *

in_slime_chunk(pos)

+ *

Boolean indicating if the given block position is in a slime chunk.

*

top(type, pos)

*

Returns the Y value of the topmost block at given x, z coords (y value of a block is not important), according to the @@ -624,7 +627,7 @@ public class CarpetExpression * top('ocean_floor', x, y, z) => 41 * *

loaded(pos)

- *

Boolean function, true if the block is accessible forthe game mechanics. + *

Boolean function, true if the block is accessible for the game mechanics. * Normally scarpet doesn't check if operates on * loaded area - the game will automatically load missing blocks. We see this as advantage. * Vanilla fill/clone commands only check the specified corners for loadness.

@@ -1019,6 +1022,19 @@ public class CarpetExpression this.expr.addLazyFunction("blast_resistance", -1, (c, t, lv) -> genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getBlastResistance()))); + this.expr.addLazyFunction("in_slime_chunk", -1, (c, t, lv) -> + { + BlockPos pos = BlockValue.fromParams((CarpetContext)c, lv, 0).block.getPos(); + ChunkPos chunkPos = new ChunkPos(pos); + Value ret = new NumericValue(ChunkRandom.create( + chunkPos.x, chunkPos.z, + ((CarpetContext)c).s.getWorld().getSeed(), + 987234911L + ).nextInt(10) == 0); + return (_c, _t) -> ret; + }); + + this.expr.addLazyFunction("top", -1, (c, t, lv) -> { String type = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT); @@ -2959,6 +2975,8 @@ public class CarpetExpression * loop(1000,game_tick()) // runs the game as fast as it can for 1000 ticks * loop(1000,game_tick(100)) // runs the game twice as slow for 1000 ticks * + *

seed()

+ *

Returns current world seed.

*

current_dimension()

*

Returns current dimension that the script runs in.

*

in_dimension(smth, expr)

@@ -3018,9 +3036,9 @@ public class CarpetExpression *
  • igloo: Igloo
  • *
  • shipwreck: Shipwreck, version1?
  • *
  • shipwreck2: Shipwreck, version2?
  • - *
  • witchhut
  • - *
  • oceanruin, oceanruin_small, oceanruin_tall: Stone variants of ocean ruins.
  • - *
  • oceanruin_warm, oceanruin_warm_small, oceanruin_warm_tall: Sandstone variants of ocean ruins.
  • + *
  • witch_hut
  • + *
  • ocean_ruin, ocean_ruin_small, ocean_ruin_tall: Stone variants of ocean ruins.
  • + *
  • ocean_ruin_warm, ocean_ruin_warm_small, ocean_ruin_warm_tall: Sandstone variants of ocean ruins.
  • *
  • treasure: A treasure chest. Yes, its a whole structure.
  • *
  • pillager_outpost: A pillager outpost.
  • *
  • mineshaft: A mineshaft.
  • @@ -3401,6 +3419,12 @@ public class CarpetExpression return (cc, tt) -> Value.TRUE; }); + this.expr.addLazyFunction("seed", -1, (c, t, lv) -> { + ServerCommandSource s = ((CarpetContext)c).s; + Value ret = new NumericValue(s.getWorld().getSeed()); + return (cc, tt) -> ret; + }); + this.expr.addLazyFunction("current_dimension", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; Value retval = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.DIMENSION_TYPE.getId(s.getWorld().dimension.getType()))); diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index f065a4e8..a486b3fe 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -43,6 +43,7 @@ public class CarpetScriptServer private static final List bundledModuleData = new ArrayList(){{ add(new BundledModule("camera", false)); + add(new BundledModule("structures", false)); add(new BundledModule("event_test", false)); add(new BundledModule("stats_test", false)); add(new BundledModule("math", true)); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index d5ad5457..37f90bc6 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -1312,7 +1312,7 @@ public class Expression LazyValue boundedLHS; if (v1 instanceof ListValue || v1 instanceof MapValue) { - ((ListValue) v1).append(v2); + ((AbstractListValue) v1).append(v2); boundedLHS = (cc, tt)-> v1; } else @@ -1626,7 +1626,7 @@ public class Expression * With break and continue statements, the supplied value can be used as a boolean check instead.

    *
          *     filter(range(100), !(_%5), _*_>1000)  => [0, 5, 10, 15, 20, 25, 30]
    -     *     map(filter(entities_list('all'),_=='Witch'), query(_,'pos') )  => [[1082.5, 57, 1243.5]]
    +     *     map(filter(entity_list('*'),_=='Witch'), query(_,'pos') )  => [[1082.5, 57, 1243.5]]
          * 
    * *

    first(list,expr(_,_i))

    @@ -2720,12 +2720,15 @@ public class Expression * length('foo') => 3 * * - *

    rand(expr)

    + *

    rand(expr), rand(expr, seed)

    *

    returns a random number from 0.0 * (inclusive) to expr (exclusive). * In boolean context (in conditions, boolean functions, or bool), returns * false if the randomly selected value is less than 1. This means that rand(2) returns true half - * of the time and rand(5) returns true for 80% (4/5) of the time

    + * of the time and rand(5) returns true for 80% (4/5) of the time. If seed is not provided, uses a random seed. + * If seed is provided, each consecutive call to rand() will act like 'next' call to the same random object. + * Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number (per app), then your + * random sequence will revert to the beginning.

    *
          * map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
          * map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
    @@ -2982,7 +2985,12 @@ public class Expression
             addUnaryFunction("type", v -> new StringValue(v.getTypeString()));
     
             addUnaryFunction("length", v -> new NumericValue(v.length()));
    -        addLazyFunction("rand", 1, (c, t, lv) -> {
    +        addLazyFunction("rand", -1, (c, t, lv) -> {
    +            int argsize = lv.size();
    +            Random randomizer = Expression.randomizer;
    +            if (argsize != 1 && argsize != 2)
    +                throw new InternalExpressionException("'rand' takes one (range) or two arguments (range and seed)");
    +            if (argsize == 2) randomizer = c.host.getRandom(NumericValue.asNumber(lv.get(1).evalValue(c)).getLong());
                 Value argument = lv.get(0).evalValue(c);
                 if (argument instanceof ListValue)
                 {
    diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java
    index ad2ba58f..63ef1dc2 100644
    --- a/src/main/java/carpet/script/ScriptHost.java
    +++ b/src/main/java/carpet/script/ScriptHost.java
    @@ -4,11 +4,13 @@ import carpet.script.bundled.Module;
     import carpet.script.exception.InternalExpressionException;
     import carpet.script.value.FunctionValue;
     import carpet.script.value.Value;
    +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
     import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
     
     import java.util.HashMap;
     import java.util.List;
     import java.util.Map;
    +import java.util.Random;
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.function.Predicate;
     import java.util.stream.Stream;
    @@ -16,6 +18,14 @@ import java.util.stream.Stream;
     public abstract class ScriptHost
     {
         public static Map systemGlobals = new ConcurrentHashMap<>();
    +    private static Map randomizers = new Long2ObjectOpenHashMap<>();
    +
    +    public Random getRandom(long aLong)
    +    {
    +        if (randomizers.size() > 1024)
    +            randomizers.clear();
    +        return randomizers.computeIfAbsent(aLong, Random::new);
    +    }
     
     
         public static class ModuleData
    diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java
    index c79ca8a5..7bd65d1a 100644
    --- a/src/main/java/carpet/script/value/MapValue.java
    +++ b/src/main/java/carpet/script/value/MapValue.java
    @@ -124,7 +124,7 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa
         @Override
         public void append(Value v)
         {
    -        put(v);
    +        map.put(v, Value.NULL);
         }
     
         @Override
    diff --git a/src/main/resources/assets/carpet/scripts/structures.sc b/src/main/resources/assets/carpet/scripts/structures.sc
    new file mode 100644
    index 00000000..a6b46b07
    --- /dev/null
    +++ b/src/main/resources/assets/carpet/scripts/structures.sc
    @@ -0,0 +1,78 @@
    +import('math', '_euclidean_sq', '_euclidean');
    +
    +global_current_structures = m();
    +global_overlay_active = false;
    +global_range = 3;
    +global_max_pieces = 4;
    +
    +__command() -> '';
    +
    +monument() -> __toggle('monument');
    +fortress() -> __toggle('fortress');
    +mansion() -> __toggle('mansion');
    +jungle_temple() -> __toggle('jungle_temple');
    +desert_temple() -> __toggle('desert_temple');
    +end_city() -> __toggle('end_city');
    +igloo() -> __toggle('igloo');
    +shipwreck() -> __toggle('shipwreck');
    +witch_hut() -> __toggle('witch_hut');
    +stronghold() -> __toggle('stronghold');
    +ocean_ruin() -> __toggle('ocean_ruin');
    +treasure() -> __toggle('treasure');
    +pillager_outpost() -> __toggle('pillager_outpost');
    +mineshaft() -> __toggle('mineshaft');
    +village() -> __toggle('village');
    +
    +__toggle(structure) ->
    +(
    +    p = player()
    +    if( has(global_current_structures:structure),
    +        delete(global_current_structures:structure);
    +        'disabled '+structure+' overlays';
    +    ,
    +        global_current_structures:structure = null;
    +        if (!global_overlay_active, __structure_renderer(p~'name'));
    +        'enabled '+structure+' overlays';
    +    )
    +);
    +
    +__structure_renderer(player_name) ->
    +(
    +    if (length(global_current_structures)==0, global_overlay_active = false; return());
    +    p = player(player_name);
    +    if (!p, global_overlay_active = false; return());
    +    global_overlay_active = true;
    +	in_dimension(p,
    +	    ppos = pos(p);
    +	    starts = m();
    +		for(range(-global_range,global_range), cx =_;
    +			for (range(-global_range,global_range), cz = _;
    +				ref_pos = ppos + l(16*cx,0,16*cz);
    +				for(filter(structure_references(ref_pos), has(global_current_structures:_)),
    +				    name = _;
    +				    for(structure_references(ref_pos, name),
    +				        starts += l(_, 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);
    +		    if (!structure_pieces, continue()); // messed up references - shouldn't happen
    +		    l(from, to) = structure_data;
    +		    total_size = _euclidean(from, to);
    +		    density = max(10, total_size/10);
    +		    particle_rect('dust 0 1 1 2', from, to+1, density);
    +		    structure_pieces = slice(sort_key(structure_pieces, _euclidean_sq((_:2+_:3)/2,ppos)), 0, global_max_pieces);
    +		    for (structure_pieces, l(piece, direction, from, to) = _;
    +		        factor = 1-_i/global_max_pieces/2;
    +		        total_size = _euclidean(from, to);
    +                density = max(_i+1, total_size/10);
    +		        particle_rect(str('dust %.2f %.2f 1 1',factor, factor), from, to+1, density);
    +		    )
    +		)
    +	);
    +	schedule(3, '__structure_renderer', player_name);
    +);
    \ No newline at end of file