diff --git a/.gitignore b/.gitignore
index 9563893d..7c9ff93b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@
build/
out/
classes/
+publish/
+docs/
# idea
diff --git a/build.gradle b/build.gradle
index 68e69288..0852c2e2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
archivesBaseName = project.archives_base_name
-version = project.mod_version
+version = project.minecraft_version+'-'+project.mod_version
group = project.maven_group
minecraft {
@@ -76,5 +76,8 @@ publishing {
repositories {
// uncomment to publish to the local maven
// mavenLocal()
+ maven {
+ url "$projectDir/publish"
+ }
}
}
diff --git a/gradle.properties b/gradle.properties
index 5a5a3144..e73bb52d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G
loader_version=0.4.8+build.155
# Mod Properties
- mod_version = 0.1a
+ mod_version = 1.0.0+v190627
maven_group = carpet
archives_base_name = fabric-carpet
diff --git a/src/main/java/carpet/commands/CarpetCommand.java b/src/main/java/carpet/commands/CarpetCommand.java
index ab64394a..cbea2e8f 100644
--- a/src/main/java/carpet/commands/CarpetCommand.java
+++ b/src/main/java/carpet/commands/CarpetCommand.java
@@ -8,7 +8,9 @@ import carpet.utils.Messenger;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.BaseText;
@@ -23,6 +25,14 @@ import static net.minecraft.server.command.CommandSource.suggestMatching;
public class CarpetCommand
{
+ private static ParsedRule> getRule(CommandContext ctx) throws CommandSyntaxException
+ {
+ String ruleName = StringArgumentType.getString(ctx, "rule");
+ ParsedRule> rule = SettingsManager.getRule(ruleName);
+ if (rule == null)
+ throw new SimpleCommandExceptionType(Messenger.c("rb Unknown rule: "+ruleName)).create();
+ return rule;
+ }
public static void register(CommandDispatcher dispatcher)
{
LiteralArgumentBuilder literalargumentbuilder = literal("carpet").requires((player) ->
@@ -41,30 +51,27 @@ public class CarpetCommand
suggests( (c, b)->suggestMatching(SettingsManager.getCategories(), b)).
executes( (c) -> listSettings(c.getSource(),
String.format("CarpetMod Settings matching \"%s\"", StringArgumentType.getString(c, "tag")),
- CarpetServer.settingsManager.getRulesMatching(StringArgumentType.getString(c, "tag"))))));
+ CarpetServer.settingsManager.getRulesMatching(StringArgumentType.getString(c, "tag")))))).
+ then(literal("removeDefault").
+ requires(s -> !CarpetServer.settingsManager.locked).
+ then(argument("rule", StringArgumentType.word()).
+ suggests( (c, b) -> suggestMatching(SettingsManager.getRules().stream().map(r -> r.name), b)).
+ executes((c) -> removeDefault(c.getSource(), getRule(c))))).
+ then(literal("setDefault").
+ requires(s -> !CarpetServer.settingsManager.locked).
+ then(argument("rule", StringArgumentType.word()).
+ suggests( (c, b) -> suggestMatching(SettingsManager.getRules().stream().map(r -> r.name), b)).
+ then(argument("value", StringArgumentType.word()).
+ suggests((c, b)-> suggestMatching(getRule(c).options, b)).
+ executes((c) -> setDefault(c.getSource(), getRule(c), StringArgumentType.getString(c, "value")))))).
+ then(argument("rule", StringArgumentType.word()).
+ suggests( (c, b) -> suggestMatching(SettingsManager.getRules().stream().map(r -> r.name), b)).
+ requires(s -> !CarpetServer.settingsManager.locked ).
+ executes( (c) -> displayRuleMenu(c.getSource(),getRule(c))).
+ then(argument("value", StringArgumentType.word()).
+ suggests((c, b)-> suggestMatching(getRule(c).options,b)).
+ executes((c) -> setRule(c.getSource(), getRule(c), StringArgumentType.getString(c, "value")))));
- for (ParsedRule> rule: SettingsManager.getRules())
- {
- literalargumentbuilder.then(literal(rule.name).executes( (context) ->
- displayRuleMenu(context.getSource(),rule)));
- literalargumentbuilder.then(literal("removeDefault").
- requires(s -> !CarpetServer.settingsManager.locked).
- then(literal(rule.name).executes((context) ->
- removeDefault(context.getSource(), rule))));
- literalargumentbuilder.then(literal(rule.name).
- requires(s -> !CarpetServer.settingsManager.locked).
- then(argument("value", StringArgumentType.word()).
- suggests((c, b)-> suggestMatching(rule.options,b)).
- executes((context) ->
- setRule(context.getSource(), rule, StringArgumentType.getString(context, "value")))));
- literalargumentbuilder.then(literal("setDefault").
- requires(s -> !CarpetServer.settingsManager.locked).
- then(literal(rule.name).
- then(argument("value", StringArgumentType.word()).
- suggests((c, b)-> suggestMatching(rule.options,b)).
- executes((context) ->
- setDefault(context.getSource(), rule, StringArgumentType.getString(context, "value"))))));
- }
dispatcher.register(literalargumentbuilder);
}
diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java
index b2f0bfed..2068990f 100644
--- a/src/main/java/carpet/commands/ScriptCommand.java
+++ b/src/main/java/carpet/commands/ScriptCommand.java
@@ -14,6 +14,8 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.block.Block;
import net.minecraft.block.pattern.CachedBlockPosition;
import net.minecraft.command.arguments.BlockPosArgumentType;
@@ -31,6 +33,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -40,6 +43,29 @@ import static net.minecraft.server.command.CommandSource.suggestMatching;
public class ScriptCommand
{
+
+ private static CompletableFuture suggestCode(
+ CommandContext context,
+ SuggestionsBuilder suggestionsBuilder
+ )
+ {
+ ScriptHost currentHost = getHost(context);
+ String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
+ int strlen = previous.length();
+ StringBuilder lastToken = new StringBuilder();
+ for (int idx = strlen-1; idx >=0; idx--)
+ {
+ char ch = previous.charAt(idx);
+ if (Character.isLetterOrDigit(ch) || ch == '_') lastToken.append(ch); else break;
+ }
+ if (lastToken.length() == 0)
+ return suggestionsBuilder.buildFuture();
+ String prefix = lastToken.reverse().toString();
+ String previousString = previous.substring(0,previous.length()-prefix.length()) ;
+ ExpressionInspector.suggestFunctions(currentHost, previousString, prefix).forEach(text -> suggestionsBuilder.suggest(previousString+text));
+ return suggestionsBuilder.buildFuture();
+ }
+
public static void register(CommandDispatcher dispatcher)
{
LiteralArgumentBuilder b = literal("globals").
@@ -50,7 +76,7 @@ public class ScriptCommand
executes( (cc) -> { CarpetServer.scriptServer.stopAll = false; return 1;});
LiteralArgumentBuilder l = literal("run").
requires((player) -> player.hasPermissionLevel(2)).
- then(argument("expr", StringArgumentType.greedyString()).
+ then(argument("expr", StringArgumentType.greedyString()).suggests((cc, bb) -> suggestCode(cc, bb)).
executes((cc) -> compute(
cc,
StringArgumentType.getString(cc, "expr"))));
@@ -113,6 +139,7 @@ public class ScriptCommand
then(argument("from", BlockPosArgumentType.create()).
then(argument("to", BlockPosArgumentType.create()).
then(argument("expr", StringArgumentType.greedyString()).
+ suggests((cc, bb) -> suggestCode(cc, bb)).
executes( (cc) -> scriptScan(
cc,
BlockPosArgumentType.getBlockPos(cc, "origin"),
diff --git a/src/main/java/carpet/mixins/ServerChunkManagerMixin.java b/src/main/java/carpet/mixins/ServerChunkManagerMixin.java
index 77713bbf..ab20320f 100644
--- a/src/main/java/carpet/mixins/ServerChunkManagerMixin.java
+++ b/src/main/java/carpet/mixins/ServerChunkManagerMixin.java
@@ -119,7 +119,7 @@ public abstract class ServerChunkManagerMixin
SpawnReporter.spawn_cap_count.get(key) + mobCount);
}
- if (mobCount <= int_3)
+ if (mobCount <= int_3 || SpawnReporter.mock_spawns)
{
//place 0 to indicate there were spawn attempts for a category
//if (entityCategory != EntityCategory.CREATURE || world.getServer().getTicks() % 400 == 0)
@@ -136,6 +136,8 @@ public abstract class ServerChunkManagerMixin
@Inject(method = "tickChunks", at = @At("RETURN"))
private void onFinishSpawnWorldCycle(CallbackInfo ci)
{
+ LevelProperties levelProperties_1 = this.world.getLevelProperties();
+ boolean boolean_3 = levelProperties_1.getTime() % 400L == 0L;
if (SpawnReporter.track_spawns > 0L && SpawnReporter.local_spawns != null)
{
for (EntityCategory cat: EntityCategory.values())
@@ -145,9 +147,13 @@ public abstract class ServerChunkManagerMixin
int spawnTries = SpawnReporter.spawn_tries.get(cat);
if (!SpawnReporter.local_spawns.containsKey(cat))
{
- // fill mobcaps for that category so spawn got cancelled
- SpawnReporter.spawn_ticks_full.put(key,
- SpawnReporter.spawn_ticks_full.get(key)+ spawnTries);
+ if (!cat.isAnimal() || boolean_3)
+ {
+ // fill mobcaps for that category so spawn got cancelled
+ SpawnReporter.spawn_ticks_full.put(key,
+ SpawnReporter.spawn_ticks_full.get(key)+ spawnTries);
+ }
+
}
else if (SpawnReporter.local_spawns.get(cat) > 0)
{
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 088fed23..c9eeb038 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -1,58 +1,66 @@
package carpet.script;
import carpet.CarpetServer;
-import carpet.script.value.NullValue;
-import carpet.settings.CarpetSettings;
-import carpet.helpers.FeatureGenerator;
import carpet.fakes.MinecraftServerInterface;
+import carpet.helpers.FeatureGenerator;
+import carpet.script.Fluff.TriFunction;
import carpet.script.exception.CarpetExpressionException;
import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
-import carpet.script.Fluff.TriFunction;
import carpet.script.value.BlockValue;
import carpet.script.value.EntityValue;
import carpet.script.value.LazyListValue;
import carpet.script.value.ListValue;
+import carpet.script.value.NBTSerializableValue;
+import carpet.script.value.NullValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
+import carpet.settings.CarpetSettings;
import carpet.utils.BlockInfo;
import carpet.utils.Messenger;
+import com.google.common.collect.Sets;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.Block;
+import net.minecraft.block.BlockPlacementEnvironment;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.client.network.packet.PlaySoundIdS2CPacket;
+import net.minecraft.command.arguments.ItemStackArgument;
+import net.minecraft.command.arguments.ParticleArgumentType;
import net.minecraft.enchantment.Enchantments;
+import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.entity.ItemEntity;
+import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.decoration.ArmorStandEntity;
-import net.minecraft.item.Items;
-import net.minecraft.server.command.ServerCommandSource;
-import net.minecraft.command.arguments.ParticleArgumentType;
-import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
-import net.minecraft.client.network.packet.PlaySoundIdS2CPacket;
+import net.minecraft.item.Items;
+import net.minecraft.nbt.CompoundTag;
import net.minecraft.particle.ParticleEffect;
-import net.minecraft.block.BlockPlacementEnvironment;
-import net.minecraft.server.MinecraftServer;
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.state.property.Property;
-import net.minecraft.state.StateFactory;
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;
+import net.minecraft.sound.BlockSoundGroup;
+import net.minecraft.sound.SoundCategory;
+import net.minecraft.state.StateFactory;
+import net.minecraft.state.property.Property;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
-import net.minecraft.sound.SoundCategory;
-import net.minecraft.util.math.Box;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Box;
import net.minecraft.util.math.EulerRotation;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
+import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
-import net.minecraft.server.world.ServerWorld;
-import net.minecraft.world.Heightmap;
import net.minecraft.world.loot.context.LootContext;
import net.minecraft.world.loot.context.LootContextParameters;
import org.apache.commons.lang3.tuple.Pair;
@@ -94,6 +102,7 @@ public class CarpetExpression
private ServerCommandSource source;
private BlockPos origin;
private Expression expr;
+ Expression getExpr() {return expr;}
private static long tickStart = 0L;
private static boolean stopAll = false;
@@ -272,12 +281,13 @@ public class CarpetExpression
* Specifying blocks
* block(x, y, z), block(state)
* Returns either a block from specified location, or block with a specific state
- * (as used by /setblock command), so allowing for block properties etc.
- * Blocks can be referenced everywhere by its simple name, but its only used in its default state
+ * (as used by /setblock command), so allowing for block properties, block entity data etc.
+ * Blocks otherwise can be referenced everywhere by its simple string name, but its only used in its default state
*
* block('air') => air
* block('iron_trapdoor[half=top]') => iron_trapdoor
* block(0,0,0) == block('bedrock') => 1
+ * block('hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') => hopper
*
* World Manipulation
* All the functions below can be used with block value, queried with coord triple, or 3-long list.
@@ -306,6 +316,21 @@ public class CarpetExpression
* set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - no parsing of properties
* set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor
* set(x,y,z,block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
+ * set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data
+ *
+ *
place_item(item, pos, facing?, sneak?)
+ * Places a given item in the world like it was placed by a player. Item names are default minecraft item name,
+ * less the minecraft prefix. Default facing is 'up', but there are other options: 'down', 'north', 'east',
+ * 'south', 'west', but also there are other secondary directions important for placement of blocks like stairs, doors, etc.
+ * Try experiment with options like 'north-up' which is placed facing north with cursor pointing to the upper part of the block,
+ * or 'up-north', which means a block placed facing up (player looking down) and placed smidge away of the block center
+ * towards north. Optional sneak is a boolean indicating if a player would be sneaking while placing the block -
+ * this option only affects placement of chests and scaffolding at the moment.
+ * Returns true if placement was successful, false otherwise.
+ *
+ * place_item('stone',x,y,z) // places a stone block on x,y,z block
+ * place_item('piston,x,y,z,'down') // places a piston facing down
+ * place_item('carrot',x,y,z) // attempts to plant a carrot plant. Returns true if could place carrots at that position.
*
* update(pos)
* Causes a block update at position.
@@ -343,7 +368,12 @@ public class CarpetExpression
* property(block('iron_trapdoor[half=top]'),'powered') => false
* bool(property(block('iron_trapdoor[half=top]'),'powered')) => 0
*
- *
+ * block_data(pos)
+ * Return NBT string associated with specific location, or null if the block does not carry block data.
+ * Can be currently used to match specific information from it, or use it to copy to another block
+ *
+ * block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }'
+ *
* solid(pos)
* Boolean function, true if the block is solid
* air(pos)
@@ -539,6 +569,20 @@ public class CarpetExpression
return (c_, t_) -> retval;
});
+ this.expr.addLazyFunction("block_data", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ if (lv.size() == 0)
+ {
+ throw new InternalExpressionException("Block requires at least one parameter");
+ }
+ CompoundTag tag = BlockValue.fromParams(cc, lv, 0, true).block.getData();
+ if (tag.isEmpty())
+ return (c_, t_) -> Value.NULL;
+ Value retval = new NBTSerializableValue(tag);
+ return (c_, t_) -> retval;
+ });
+
this.expr.addLazyFunction("pos", 1, (c, t, lv) ->
{
Value arg = lv.get(0).evalValue(c);
@@ -556,7 +600,7 @@ public class CarpetExpression
if (e == null)
throw new InternalExpressionException("Null entity");
Value retval = ListValue.of(new NumericValue(e.x), new NumericValue(e.y), new NumericValue(e.z));
- return(c_, t_) -> retval;
+ return (c_, t_) -> retval;
}
else
{
@@ -607,7 +651,8 @@ public class CarpetExpression
genericStateTest(c, "blast_resistance", lv, (s, p, w) -> new NumericValue(s.getBlock().getBlastResistance())));
- this.expr.addLazyFunction("top", -1, (c, t, lv) -> {
+ this.expr.addLazyFunction("top", -1, (c, t, lv) ->
+ {
String type = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT);
Heightmap.Type htype;
switch (type)
@@ -709,17 +754,36 @@ public class CarpetExpression
sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState);
}
}
- if (sourceBlockState == targetBlockState)
+
+ CompoundTag data = sourceLocator.block.getData();
+
+ if (sourceBlockState == targetBlockState && data == null)
return (c_, t_) -> Value.FALSE;
CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
- cc.s.getWorld().setBlockState(targetLocator.block.getPos(), sourceBlockState, 2);
+ BlockPos targetPos = targetLocator.block.getPos();
+ cc.s.getWorld().setBlockState(targetPos, sourceBlockState, 2);
+
+ if ( data != null)
+ {
+ BlockEntity be = world.getBlockEntity(targetPos);
+ if (be != null)
+ {
+ CompoundTag destTag = data.method_10553();
+ destTag.putInt("x", targetPos.getX());
+ destTag.putInt("y", targetPos.getY());
+ destTag.putInt("z", targetPos.getZ());
+ be.fromTag(destTag);
+ be.markDirty();
+ }
+ }
+
CarpetSettings.impendingFillSkipUpdates = false;
Value retval = new BlockValue(sourceBlockState, world, targetLocator.block.getPos());
return (c_, t_) -> retval;
});
- this.expr.addLazyFunction("destroy", -1, (c, t, lv) -> {
-
+ this.expr.addLazyFunction("destroy", -1, (c, t, lv) ->
+ {
CarpetContext cc = (CarpetContext)c;
ServerWorld world = cc.s.getWorld();
BlockValue.LocatorResult locator = BlockValue.fromParams(cc, lv, 0);
@@ -756,8 +820,10 @@ public class CarpetExpression
return (c_, t_) -> Value.TRUE;
});
- this.expr.addLazyFunction("harvest", -1, (c, t, lv) -> {
-
+ this.expr.addLazyFunction("harvest", -1, (c, t, lv) ->
+ {
+ if (lv.size()<2)
+ throw new InternalExpressionException("harvest takes at least 2 parameters: entity and block, or position, to harvest");
CarpetContext cc = (CarpetContext)c;
World world = cc.s.getWorld();
Value entityValue = lv.get(0).evalValue(cc);
@@ -776,11 +842,57 @@ public class CarpetExpression
ItemStack itemstack2 = player.getMainHandStack();
itemstack2.postMine(player.world, state, where, player);
ItemStack itemstack1 = itemstack2.isEmpty() ? ItemStack.EMPTY : itemstack2.copy();
- state.getBlock().afterBreak(world, player, where,state, be, itemstack1);
+ state.getBlock().afterBreak(world, player, where, state, be, itemstack1);
world.playLevelEvent(null, 2001, where, Block.getRawIdFromState(state));
return (c_, t_) -> Value.TRUE;
});
+ this.expr.addLazyFunction("place_item", -1, (c, t, lv) ->
+ {
+ if (lv.size()<2)
+ throw new InternalExpressionException("place_item takes at least 2 parameters: item and block, or position, to place onto");
+ CarpetContext cc = (CarpetContext) c;
+ String itemString = lv.get(0).evalValue(c).getString();
+ BlockValue.VectorLocator locator = BlockValue.locateVec(cc, lv, 1);
+ ItemStackArgument stackArg = NBTSerializableValue.parseItem(itemString);
+ BlockPos where = new BlockPos(locator.vec);
+ String facing = "up";
+ if (lv.size() > locator.offset)
+ facing = lv.get(locator.offset).evalValue(c).getString();
+ boolean sneakPlace = false;
+ if (lv.size() > locator.offset+1)
+ sneakPlace = lv.get(locator.offset+1).evalValue(c).getBoolean();
+ if (stackArg.getItem() instanceof BlockItem)
+ {
+ BlockItem blockItem = (BlockItem) stackArg.getItem();
+ BlockValue.PlacementContext ctx;
+ try
+ {
+ ctx = BlockValue.PlacementContext.from(cc.s.getWorld(), where, facing, sneakPlace, stackArg.createStack(1, false));
+ }
+ catch (CommandSyntaxException e)
+ {
+ throw new InternalExpressionException(e.getMessage());
+ }
+ if (!ctx.canPlace())
+ return (_c, _t) -> Value.FALSE;
+ BlockState placementState = blockItem.getBlock().getPlacementState(ctx);
+ if (placementState != null)
+ {
+ if (placementState.canPlaceAt(cc.s.getWorld(), where))
+ {
+ CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
+ cc.s.getWorld().setBlockState(where, placementState, 2);
+ CarpetSettings.impendingFillSkipUpdates = false;
+ BlockSoundGroup blockSoundGroup = placementState.getSoundGroup();
+ cc.s.getWorld().playSound(null, where, blockSoundGroup.getPlaceSound(), SoundCategory.BLOCKS, (blockSoundGroup.getVolume() + 1.0F) / 2.0F, blockSoundGroup.getPitch() * 0.8F);
+ return (_c, _t) -> Value.TRUE;
+ }
+ }
+ }
+ return (_c, _t) -> Value.FALSE;
+ });
+
this.expr.addLazyFunction("blocks_movement", -1, (c, t, lv) ->
booleanStateTest(c, "blocks_movement", lv, (s, p) ->
!s.canPlaceAtSide(((CarpetContext) c).s.getWorld(), p, BlockPlacementEnvironment.LAND)));
@@ -789,11 +901,11 @@ public class CarpetExpression
stateStringQuery(c, "block_sound", lv, (s, p) ->
BlockInfo.soundName.get(s.getSoundGroup())));
- this.expr.addLazyFunction("material",-1, (c, t, lv) ->
+ this.expr.addLazyFunction("material", -1, (c, t, lv) ->
stateStringQuery(c, "material", lv, (s, p) ->
BlockInfo.materialName.get(s.getMaterial())));
- this.expr.addLazyFunction("map_colour", -1, (c, t, lv) ->
+ this.expr.addLazyFunction("map_colour", -1, (c, t, lv) ->
stateStringQuery(c, "map_colour", lv, (s, p) ->
BlockInfo.mapColourName.get(s.getTopMaterialColor(((CarpetContext)c).s.getWorld(), p))));
@@ -807,10 +919,331 @@ public class CarpetExpression
if (property == null)
return LazyValue.NULL;
Value retval = new StringValue(state.get(property).toString());
- return (_c, _t ) -> retval;
+ return (_c, _t) -> retval;
+ });
+
+
+ }
+
+ /**
+ * Inventory and Items API
+ *
+ *
Manipulating inventories of blocks and entities
+ *
Most functions in this category require inventory as the first argument. Inventory could be specified by
+ * an entity, or a block, or position (three coordinates) of a potential block with inventory.
+ * If the entity or a block doesn't
+ * have an inventory, they typically do nothing and return null.
+ *
Most items returned are in the form of
+ * a triple of item name, count, and nbt or the extra data associated with an item.
+ * Manipulating of the nbt data can be costly, but retrieving them from the tuple to match other aspects is cheap
+ *
stack_limit(item)
+ *
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable),
+ * 16 (like buckets), or 64 - rest. It is recommended to consult this, as other inventory API functions
+ * ignore normal stack limits, and it is up to the programmer to keep it at bay. As of 1.13, game checks for negative
+ * numbers and setting an item to negative is the same as empty.
+ *
+ * stack_limit('wooden_axe') => 1
+ * stack_limit('ender_pearl') => 16
+ * stack_limit('stone') => 64
+ *
+ *
inventory_size(inventory)
+ *
Returns the size of the inventory for the entity or block in question. Returns null if the block or entity
+ * don't have an inventory
+ *
+ * inventory_size(player()) => 41
+ * inventory_size(x,y,z) => 27 // chest
+ * inventory_size(block(pos)) => 5 // hopper
+ *
+ *
inventory_get(inventory, slot)
+ *
Returns the item in the corresponding inventory slot, or null if slot empty or inventory is invalid.
+ * You can use negative numbers to indicate slots counted from 'the back'.
+ *
+ * inventory_get(player(), 0) => null // nothing in first hotbar slot
+ * inventory_get(x,y,z, 5) => ['stone', 1, {}]
+ * inventory_get(player(), -1) => ['diamond_pickaxe', 1, {Damage:4}] // slightly damaged diamond pick in the offhand
+ *
+ *
+ *
inventory_set(inventory, slot, count, item?, nbt?)
+ *
Modifies or sets a stack in inventory. specify count 0 to empty the slot.
+ * If item is not specified, keeps existing item, just modifies the count. If item is provided - replaces current item.
+ * If nbt is provided - adds a tag to the stack at slot. Returns previous stack in that slot.
+ *
+ * inventory_set(player(), 0, 0) => ['stone', 64, {}] // player had a stack of stone in first hotbar slot
+ * inventory_set(player(), 0, 6) => ['diamond', 64, {}] // changed stack of diamonds in player slot to 6
+ * inventory_set(player(), 0, 1, 'diamond_axe','{Damage:5}') => null //added slightly damaged pick to first player slot
+ *
+ *
+ *
inventory_find(inventory, item, start_slot?, ), inventory_find(inventory, null, start_slot?)
+ *
Finds the first slot with a corresponding item in the inventory, or if queried with null: the first empty slot.
+ * Returns slot number if found, or null otherwise. Optional start_slot argument allows to skip all preceeding slots
+ * allowing for efficient (so not slot-by-slot) inventory search for items.
+ *
+ * inventory_find(player(), 'stone') => 0 // player has stone in first hotbar slot
+ * inventory_find(player(), null) => null // player's inventory has no empty spot
+ * while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slot) )
+ * // spits all diamonds from player inventory wherever they are
+ * inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
+ *
+ *
+ *
inventory_remove(inventory, item, amount?)
+ *
Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing
+ * happens, otherwise the given amount of items is removed wherever they are in the inventory. Returns boolean whether
+ * the removal operation was successful. Easiest way to remove a specific item from player inventory without specifying
+ * the slot.
+ *
+ * inventory_remove(player(), 'diamond') => 1 // removed diamond from player inventory
+ * inventory_remove(player(), 'diamond', 100) => 0 // player doesn't have 100 diamonds, nothing happened
+ *
+ *
+ *
drop_item(inventory, slot, amount?, )
+ *
Drops the items from indicated inventory slot, like player that Q's an item or villager, that exchanges food.
+ * You can Q items from block inventories as well. default amount is 0 - which is all from the slot. NOTE: hoppers are quick
+ * enough to pick all the queued items from their inventory anyways.
+ * Returns size of the actual dropped items.
+ *
+ * inventory_drop(player(), 0, 1) => 1 // Q's one item on the ground
+ * inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
+ *
+ *
+ */
+ public void API_InventoryManipulation()
+ {
+ this.expr.addLazyFunction("stack_limit", 1, (c, t, lv) ->
+ {
+ ItemStackArgument item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString());
+ Value res = new NumericValue(item.getItem().getMaxCount());
+ return (_c, _t) -> res;
+ });
+
+ this.expr.addLazyFunction("inventory_size", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ Value res = new NumericValue(inventoryLocator.inventory.getInvSize());
+ return (_c, _t) -> res;
+ });
+
+ //inventory_get(, ) -> item_triple
+ this.expr.addLazyFunction("inventory_get", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ if (lv.size() == inventoryLocator.offset)
+ {
+ List fullInventory = new ArrayList<>();
+ for (int i = 0, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++)
+ {
+ fullInventory.add(ListValue.fromItemStack(inventoryLocator.inventory.getInvStack(i)));
+ }
+ Value res = ListValue.wrap(fullInventory);
+ return (_c, _t) -> res;
+ }
+ int slot = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset).evalValue(c)).getLong();
+ slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory);
+ if (slot == inventoryLocator.inventory.getInvSize())
+ return (_c, _t) -> Value.NULL;
+ Value res = ListValue.fromItemStack(inventoryLocator.inventory.getInvStack(slot));
+ return (_c, _t) -> res;
+ });
+
+ //inventory_set(, , , - , )
+ this.expr.addLazyFunction("inventory_set", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ if (lv.size() < inventoryLocator.offset+2)
+ throw new InternalExpressionException("inventory_set requires at least slot number and new stack size, and optional new item");
+ int slot = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+0).evalValue(c)).getLong();
+ slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory);
+ if (slot == inventoryLocator.inventory.getInvSize())
+ return (_c, _t) -> Value.NULL;
+ int count = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong();
+ if (count == 0)
+ {
+ // clear slot
+ ItemStack removedStack = inventoryLocator.inventory.removeInvStack(slot);
+ //Value res = ListValue.fromItemStack(removedStack); // that tuple will be read only but cheaper if noone cares
+ return (_c, _t) -> ListValue.fromItemStack(removedStack);
+ }
+ if (lv.size() < inventoryLocator.offset+3)
+ {
+ ItemStack previousStack = inventoryLocator.inventory.getInvStack(slot);
+ ItemStack newStack = previousStack.copy();
+ newStack.setCount(count);
+ inventoryLocator.inventory.setInvStack(slot, newStack);
+ return (_c, _t) -> ListValue.fromItemStack(previousStack);
+ }
+ CompoundTag nbt = null; // skipping one argument
+ if (lv.size() > inventoryLocator.offset+3)
+ {
+ Value nbtValue = lv.get(inventoryLocator.offset+3).evalValue(c);
+ if (nbtValue instanceof NBTSerializableValue)
+ nbt = ((NBTSerializableValue)nbtValue).getTag();
+ else
+ nbt = new NBTSerializableValue(nbtValue.getString()).getTag();
+ }
+ ItemStackArgument newitem = NBTSerializableValue.parseItem(
+ lv.get(inventoryLocator.offset+2).evalValue(c).getString(),
+ nbt
+ );
+
+ ItemStack previousStack = inventoryLocator.inventory.getInvStack(slot);
+ try
+ {
+ inventoryLocator.inventory.setInvStack(slot, newitem.createStack(count, false));
+ }
+ catch (CommandSyntaxException e)
+ {
+ throw new InternalExpressionException(e.getMessage());
+ }
+ return (_c, _t) -> ListValue.fromItemStack(previousStack);
+ });
+
+ //inventory_find(,
- or null (first empty slot), ) -> or null
+ this.expr.addLazyFunction("inventory_find", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ ItemStackArgument itemArg = null;
+ if (lv.size() > inventoryLocator.offset)
+ {
+ Value secondArg = lv.get(inventoryLocator.offset+0).evalValue(c);
+ if (!(secondArg instanceof NullValue))
+ itemArg = NBTSerializableValue.parseItem(secondArg.getString());
+ }
+ int startIndex = 0;
+ if (lv.size() > inventoryLocator.offset+1)
+ {
+ startIndex = (int) NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong();
+ }
+ startIndex = NBTSerializableValue.validateSlot(startIndex, inventoryLocator.inventory);
+ for (int i = startIndex, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++)
+ {
+ ItemStack stack = inventoryLocator.inventory.getInvStack(i);
+ if ( (itemArg == null && stack.isEmpty()) || (itemArg != null && itemArg.getItem().equals(stack.getItem())) )
+ {
+ Value res = new NumericValue(i);
+ return (_c, _t) -> res;
+ }
+ }
+ return (_c, _t) -> Value.NULL;
+ });
+
+
+
+ //inventory_remove(,
- , ) -> bool
+
+ this.expr.addLazyFunction("inventory_remove", -1, (c, t, lv) -> {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ if (lv.size() <= inventoryLocator.offset)
+ throw new InternalExpressionException("inventory_set requires at least an item to be removed");
+ ItemStackArgument searchItem = NBTSerializableValue.parseItem(lv.get(inventoryLocator.offset).evalValue(c).getString());
+ int amount = 1;
+ if (lv.size() > inventoryLocator.offset+1)
+ {
+ amount = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong();
+ }
+ // not enough
+ if (((amount == 1) && (!inventoryLocator.inventory.containsAnyInInv(Sets.newHashSet(searchItem.getItem()))))
+ || (inventoryLocator.inventory.countInInv(searchItem.getItem()) < amount))
+ {
+ return (_c, _t) -> Value.FALSE;
+ }
+ for (int i = 0, maxi = inventoryLocator.inventory.getInvSize(); i < maxi; i++)
+ {
+ ItemStack stack = inventoryLocator.inventory.getInvStack(i);
+ if (stack.isEmpty())
+ continue;
+ if (!stack.getItem().equals(searchItem.getItem()))
+ continue;
+ int left = stack.getCount()-amount;
+ if (left > 0)
+ {
+ stack.setCount(left);
+ inventoryLocator.inventory.setInvStack(i, stack);
+ return (_c, _t) -> Value.TRUE;
+ }
+ else
+ {
+ inventoryLocator.inventory.removeInvStack(i);
+ amount -= stack.getCount();
+ }
+ }
+ if (amount > 0)
+ throw new InternalExpressionException("Something bad happened - cannot pull all items from inventory");
+ return (_c, _t) -> Value.TRUE;
+ });
+
+ //inventory_drop(, , ) -> entity_item (and sets slot) or null if cannot
+ this.expr.addLazyFunction("drop_item", -1, (c, t, lv) ->
+ {
+ CarpetContext cc = (CarpetContext) c;
+ NBTSerializableValue.InventoryLocator inventoryLocator = NBTSerializableValue.locateInventory(cc, lv, 0);
+ if (inventoryLocator == null)
+ return (_c, _t) -> Value.NULL;
+ if (lv.size() == inventoryLocator.offset)
+ throw new InternalExpressionException("slot number is required for inventory_drop");
+ int slot = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset).evalValue(c)).getLong();
+ slot = NBTSerializableValue.validateSlot(slot, inventoryLocator.inventory);
+ if (slot == inventoryLocator.inventory.getInvSize())
+ return (_c, _t) -> Value.NULL;
+ int amount = 0;
+ if (lv.size() > inventoryLocator.offset+1)
+ amount = (int)NumericValue.asNumber(lv.get(inventoryLocator.offset+1).evalValue(c)).getLong();
+ if (amount < 0)
+ throw new InternalExpressionException("Cannot throw negative number of items");
+ ItemStack stack = inventoryLocator.inventory.getInvStack(slot);
+ if (stack == null || stack.isEmpty())
+ return (_c, _t) -> Value.ZERO;
+ if (amount == 0)
+ amount = stack.getCount();
+ ItemStack droppedStack = inventoryLocator.inventory.takeInvStack(slot, amount);
+ if (droppedStack.isEmpty())
+ {
+ return (_c, _t) -> Value.ZERO;
+ }
+ Object owner = inventoryLocator.owner;
+ ItemEntity item;
+ if (owner instanceof PlayerEntity)
+ {
+ item = ((PlayerEntity) owner).dropItem(droppedStack, false, true);
+ }
+ else if (owner instanceof LivingEntity)
+ {
+ LivingEntity villager = (LivingEntity)owner;
+ // stolen from LookTargetUtil.give((VillagerEntity)owner, droppedStack, (LivingEntity) owner);
+ double double_1 = villager.y - 0.30000001192092896D + (double)villager.getStandingEyeHeight();
+ item = new ItemEntity(villager.world, villager.x, double_1, villager.z, droppedStack);
+ Vec3d vec3d_1 = villager.getRotationVec(1.0F).normalize().multiply(0.3);// new Vec3d(0, 0.3, 0);
+ item.setVelocity(vec3d_1);
+ item.setToDefaultPickupDelay();
+ cc.s.getWorld().spawnEntity(item);
+ }
+ else
+ {
+ Vec3d point = new Vec3d(inventoryLocator.position);
+ item = new ItemEntity(cc.s.getWorld(), point.x+0.5, point.y+0.5, point.z+0.5, droppedStack);
+ item.setToDefaultPickupDelay();
+ cc.s.getWorld().spawnEntity(item);
+ }
+ inventoryLocator.inventory.markDirty();
+ Value res = new NumericValue(item.getStack().getCount());
+ return (_c, _t) -> res;
});
}
+
/**
*
Entity API
*
@@ -859,6 +1292,12 @@ public class CarpetExpression
*
Unlike with blocks, that use plethora of vastly different querying functions, entities are queried with
* query function and altered via modify function. Type of information needed or
* values to be modified are different for each call
+ *
Using ~ (in) operator is an alias for query. Especially useful if a statement has
+ * no arguments, which in this case can be radically simplified
+ *
+ * query(p, 'name') <=> p ~ 'name' // much shorter and cleaner
+ * query(p, 'holds', 'offhand') <=> p ~ l('holds', 'offhand') // not really but can be done
+ *
*
query(e,'removed')
*
Boolean. True if the entity is removed
*
query(e,'id')
@@ -969,6 +1408,12 @@ public class CarpetExpression
*
feet
*
*
If slot is not specified, it defaults to the main hand.
+ *
query(e,'selected_slot')
+ *
Number indicating the selected slot of entity inventory. Currently only applicable to players.
+ *
query(e,'facing', order?)
+ *
Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating
+ * primary directions where entity is looking at. From most prominent (order 0) to opposite (order 5, or -1)
+ *
*
query(e,'nbt',path?)
*
Returns full NBT of the entity. If path is specified, it fetches only that portion of the NBT,
* that corresponds to the path. For specification of path attribute, consult
@@ -2051,6 +2496,7 @@ public class CarpetExpression
API_BlockManipulation();
API_EntityManipulation();
+ API_InventoryManipulation();
API_IteratingOverAreasOfBlocks();
API_AuxiliaryAspects();
}
@@ -2092,6 +2538,7 @@ public class CarpetExpression
*
The last method is the one that world edit is using (part of carpet mod). It turns out that the outline method with 32.5 radius,
* fill method with round function and draw command are equivalent
*
+ * @param host .
* @param x .
* @param y .
* @param z .
@@ -2134,12 +2581,14 @@ public class CarpetExpression
/**
* /script run command
*
- *
primary way to input commands. The command executes in the context, position, and dimension of the executing
+ *
Primary way to input commands. The command executes in the context, position, and dimension of the executing
* player, commandblock, etc... The command receives 4 variables, x, y, z
* and p indicating position and the executing entity of the command.
+ * You will receive tab completion suggestions as you type your code suggesting functions and global variables.
* It is advisable to use /execute in ... at ... as ... run script run ... or similar, to simulate running
* commands in a different scope
*
+ * @param host .
* @param pos .
* @return .
*/
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index 9515d8bb..1410f39e 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -19,6 +19,7 @@ import carpet.script.value.ListValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
+import carpet.settings.CarpetSettings;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -31,6 +32,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.Stack;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -349,6 +351,7 @@ public class Expression implements Cloneable
boolean isAnOperator(String opname) { return operators.containsKey(opname) || operators.containsKey(opname+"u");}
private Map functions = new HashMap<>();
+ Set getFunctionNames() {return functions.keySet();}
//static final Map globalFunctions = new HashMap<>();
@@ -856,9 +859,10 @@ public class Expression implements Cloneable
* Ability to combine more statements into one expression, with functions, passing parameters, and global and outer
* scoping allow to organize even larger scripts
* Control flow
- * return(expr)
+ * return(expr?)
* Sometimes its convenient to break the organized control flow, or it is not practical to pass
* the final result value of a function to the last statement, in this case a return statement can be used
+ * If no argument is provided - returns null value.
*
* def() -> (
* expr1;
@@ -870,12 +874,14 @@ public class Expression implements Cloneable
*
* In general its cheaper to leave the last expression as a return value, rather than calling returns everywhere,
* but it would often lead to a messy code.
- * exit(expr)
- * It terminates entire program passing expr as the result of the program execution.
- * try(expr, catch_expr(_)) ... throw(value)
+ * exit(expr?)
+ * It terminates entire program passing expr as the result of the program execution, or null if omitted.
+ * try(expr, catch_expr(_)?) ... throw(value?)
* try function evaluates expression, and continues further unless throw function is called
* anywhere inside expr. In that case the catch_expr is evaluates with _ set
- * to the argument throw was called with. This mechanism allows to terminate large portion of a convoluted
+ * to the argument throw was called with.
+ * This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns null as well
+ * This mechanism allows to terminate large portion of a convoluted
* call stack and continue program execution. There is only one level of exceptions currently in carpet, so if the inner
* function also defines the try catchment area, it will received the exception first, but it can technically
* rethrow the value its getting for the outer scope. Unhandled throw acts like an exit statement.
@@ -961,12 +967,14 @@ public class Expression implements Cloneable
return (cc, tt) -> new StringValue("OK");
});
- addUnaryFunction("exit", (v) -> { throw new ExitStatement(v); });
- addUnaryFunction("return", (v) -> { throw new ReturnStatement(v); });
- addUnaryFunction("throw", (v)-> {throw new ThrowStatement(v); });
+ addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); });
+ addFunction("return", (lv) -> { throw new ReturnStatement(lv.size()==0?Value.NULL:lv.get(0));} );
+ addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); });
- addLazyFunction("try", 2, (c, t, lv) ->
+ addLazyFunction("try", -1, (c, t, lv) ->
{
+ if (lv.size()==0)
+ throw new InternalExpressionException("try needs at least an expression block");
try
{
Value retval = lv.get(0).evalValue(c, t);
@@ -974,6 +982,8 @@ public class Expression implements Cloneable
}
catch (ThrowStatement ret)
{
+ if (lv.size() == 1)
+ return (c_, t_) -> Value.NULL;
LazyValue __ = c.getVariable("_");
c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_"));
Value val = lv.get(1).evalValue(c, t);
@@ -1613,15 +1623,15 @@ public class Expression implements Cloneable
* range(20, 10, -2) => [20, 18, 16, 14, 12]
*
*
- * element(list, index)
+ * get(list, index), element(list, index)(deprecated)
* Returns the value at index element from the list.
- * use negative numbers to reach elements from the end of the list. element
+ * use negative numbers to reach elements from the end of the list. get
* call will always be able to find the index. In case there is few items, it will loop over
*
- * element(l(range(10)), 5) => 5
- * element(l(range(10)), -1) => 9
- * element(l(range(10)), 10) => 0
- * element(l(range(10)), 93) => 3
+ * get(l(range(10)), 5) => 5
+ * get(l(range(10)), -1) => 9
+ * get(l(range(10)), 10) => 0
+ * get(l(range(10)), 93) => 3
*
*
* put(list, index, values ...), put(list, null, values ...)
@@ -1833,11 +1843,10 @@ public class Expression implements Cloneable
return LazyListValue.range(from, to, step);
});
- addBinaryFunction("element", (v1, v2) -> {
+ addBinaryFunction("get", (v1, v2) ->
+ {
if (v1 instanceof LazyListValue || !(v1 instanceof ListValue))
- {
- throw new InternalExpressionException("First argument of element should be a list");
- }
+ throw new InternalExpressionException("First argument of get should be a list");
List items = ((ListValue)v1).getItems();
long index = NumericValue.asNumber(v2).getLong();
int numitems = items.size();
@@ -1846,6 +1855,21 @@ public class Expression implements Cloneable
index = index % numitems;
return items.get((int)index);
});
+
+ //Deprecated, use "get" instead
+ addBinaryFunction("element", (v1, v2) ->
+ {
+ if (v1 instanceof LazyListValue || !(v1 instanceof ListValue))
+ throw new InternalExpressionException("First argument of get should be a list");
+ List items = ((ListValue)v1).getItems();
+ long index = NumericValue.asNumber(v2).getLong();
+ int numitems = items.size();
+ long range = abs(index)/numitems;
+ index += (range+2)*numitems;
+ index = index % numitems;
+ return items.get((int)index);
+ });
+
addFunction("put", (lv) ->
{
if(lv.size()<3)
@@ -2224,7 +2248,7 @@ public class Expression implements Cloneable
* number('2')+number('2') => 4
*
*
- * str(expr, params? ... )
+ * str(expr, params? ... ), str(expr, param_list)
*
* Returns a formatted string representing expression.
* Accepts formatting style accepted by String.format.
@@ -2247,6 +2271,7 @@ public class Expression implements Cloneable
* str('3bar') => null
* str(2)+str(2) => 22
* str('pi: %.2f',pi) => 'pi: 3.14'
+ * str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'
*
*
*
diff --git a/src/main/java/carpet/script/ExpressionInspector.java b/src/main/java/carpet/script/ExpressionInspector.java
index a9edc887..56d602c5 100644
--- a/src/main/java/carpet/script/ExpressionInspector.java
+++ b/src/main/java/carpet/script/ExpressionInspector.java
@@ -1,6 +1,13 @@
package carpet.script;
+import carpet.CarpetServer;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
/**
* sole purpose of this package is to provide public access to package-private methods of Expression and CarpetExpression
@@ -17,4 +24,34 @@ public class ExpressionInspector
{
return e.getName();
}
+
+ private static TreeSet scarpetFunctions;
+ private static TreeSet APIFunctions;
+ static
+ {
+ Set allFunctions = (new CarpetExpression("null", null, null)).getExpr().getFunctionNames();
+ scarpetFunctions = new TreeSet<>(new Expression("null").getFunctionNames());
+ APIFunctions = new TreeSet<>(allFunctions.stream().filter(s -> !scarpetFunctions.contains(s)).collect(Collectors.toSet()));
+ }
+
+ public static List suggestFunctions(ScriptHost host, String previous, String prefix)
+ {
+ previous = previous.replace("\\'", "");
+ int quoteCount = StringUtils.countMatches(previous,'\'');
+ if (quoteCount % 2 == 1)
+ return Collections.emptyList();
+ int maxLen = prefix.length()<3 ? (prefix.length()*2+1) : 1234;
+ List scarpetMatches = scarpetFunctions.stream().
+ filter(s -> s.startsWith(prefix) && s.length() <= maxLen).map(s -> s+"(").collect(Collectors.toList());
+ scarpetMatches.addAll(APIFunctions.stream().
+ filter(s -> s.startsWith(prefix) && s.length() <= maxLen).map(s -> s+"(").collect(Collectors.toList()));
+ // not that useful in commandline, more so in external scripts, so skipping here
+ //scarpetMatches.addAll(CarpetServer.scriptServer.events.eventHandlers.keySet().stream().
+ // filter(s -> s.startsWith(prefix) && s.length() <= maxLen).map(s -> "__"+s+"(").collect(Collectors.toList()));
+ scarpetMatches.addAll(host.globalFunctions.keySet().stream().
+ filter(s -> s.startsWith(prefix)).map(s -> s+"(").collect(Collectors.toList()));
+ scarpetMatches.addAll(host.globalVariables.keySet().stream().
+ filter(s -> s.startsWith(prefix)).collect(Collectors.toList()));
+ return scarpetMatches;
+ }
}
diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java
index f113788c..5d3ec2a8 100644
--- a/src/main/java/carpet/script/value/BlockValue.java
+++ b/src/main/java/carpet/script/value/BlockValue.java
@@ -6,16 +6,25 @@ import carpet.script.exception.InternalExpressionException;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState;
+import net.minecraft.block.entity.BlockEntity;
import net.minecraft.command.arguments.BlockArgumentParser;
import net.minecraft.block.Blocks;
+import net.minecraft.item.ItemPlacementContext;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.util.Hand;
+import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.World;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
public class BlockValue extends Value
{
@@ -24,6 +33,7 @@ public class BlockValue extends Value
private BlockState blockState;
private BlockPos pos;
private World world;
+ private CompoundTag data;
public static BlockValue fromCoords(CarpetContext c, int x, int y, int z)
{
@@ -41,7 +51,10 @@ public class BlockValue extends Value
BlockArgumentParser blockstateparser = (new BlockArgumentParser(new StringReader(str), false)).parse(true);
if (blockstateparser.getBlockState() != null)
{
- bv = new BlockValue(blockstateparser.getBlockState(), null, null);
+ CompoundTag bd = blockstateparser.getNbtData();
+ if (bd == null)
+ bd = new CompoundTag();
+ bv = new BlockValue(blockstateparser.getBlockState(), null, null, bd );
if (bvCache.size()>10000)
bvCache.clear();
bvCache.put(str, bv);
@@ -175,11 +188,44 @@ public class BlockValue extends Value
throw new InternalExpressionException("Attemted to fetch blockstate without world or stored blockstate");
}
+ public CompoundTag getData()
+ {
+ if (data != null)
+ {
+ if (data.isEmpty())
+ return null;
+ return data;
+ }
+ if (world != null && pos != null)
+ {
+ BlockEntity be = world.getBlockEntity(pos);
+ CompoundTag tag = new CompoundTag();
+ if (be == null)
+ {
+ data = tag;
+ return null;
+ }
+ data = be.toTag(tag);
+ return data;
+ }
+ throw new InternalExpressionException("Attemted to fetch block data without world or stored block data");
+ }
+
+
public BlockValue(BlockState state, World world, BlockPos position)
{
this.world = world;
blockState = state;
pos = position;
+ data = null;
+ }
+
+ public BlockValue(BlockState state, World world, BlockPos position, CompoundTag nbt)
+ {
+ this.world = world;
+ blockState = state;
+ pos = position;
+ data = nbt;
}
@@ -239,4 +285,112 @@ public class BlockValue extends Value
pitch = p;
}
}
+
+ public enum SpecificDirection {
+ UP("up",0.5, 0.0, 0.5, Direction.UP),
+
+ UPNORTH ("up-north", 0.5, 0.0, 0.4, Direction.UP),
+ UPSOUTH ("up-south", 0.5, 0.0, 0.6, Direction.UP),
+ UPEAST("up-east", 0.6, 0.0, 0.5, Direction.UP),
+ UPWEST("up-west", 0.4, 0.0, 0.5, Direction.UP),
+
+ DOWN("down", 0.5, 1.0, 0.5, Direction.DOWN),
+
+ DOWNNORTH ("down-north", 0.5, 1.0, 0.4, Direction.DOWN),
+ DOWNSOUTH ("down-south", 0.5, 1.0, 0.6, Direction.DOWN),
+ DOWNEAST("down-east", 0.6, 1.0, 0.5, Direction.DOWN),
+ DOWNWEST("down-west", 0.4, 1.0, 0.5, Direction.DOWN),
+
+
+ NORTH ("north", 0.5, 0.4, 1.0, Direction.NORTH),
+ SOUTH ("south", 0.5, 0.4, 0.0, Direction.SOUTH),
+ EAST("east", 0.0, 0.4, 0.5, Direction.EAST),
+ WEST("west", 1.0, 0.4, 0.5, Direction.WEST),
+
+ NORTHUP ("north-up", 0.5, 0.6, 1.0, Direction.NORTH),
+ SOUTHUP ("south-up", 0.5, 0.6, 0.0, Direction.SOUTH),
+ EASTUP("east-up", 0.0, 0.6, 0.5, Direction.EAST),
+ WESTUP("west-up", 1.0, 0.6, 0.5, Direction.WEST);
+
+ public String name;
+ public Vec3d hitOffset;
+ public Direction facing;
+
+ private static final Map DIRECTION_MAP = Arrays.stream(values()).collect(Collectors.toMap(SpecificDirection::getName, d -> d));
+
+
+ SpecificDirection(String name, double hitx, double hity, double hitz, Direction blockFacing)
+ {
+ this.name = name;
+ this.hitOffset = new Vec3d(hitx, hity, hitz);
+ this.facing = blockFacing;
+ }
+ private String getName()
+ {
+ return name;
+ }
+ }
+
+ public static class PlacementContext extends ItemPlacementContext {
+ private final Direction facing;
+ private final boolean sneakPlace;
+
+ public static PlacementContext from(World world, BlockPos pos, String direction, boolean sneakPlace, ItemStack itemStack)
+ {
+ SpecificDirection dir = SpecificDirection.DIRECTION_MAP.get(direction);
+ if (dir == null)
+ throw new InternalExpressionException("unknown block placement direction: "+direction);
+ BlockHitResult hitres = new BlockHitResult(new Vec3d(pos).add(dir.hitOffset), dir.facing, pos, false);
+ return new PlacementContext(world, dir.facing, sneakPlace, itemStack, hitres);
+ }
+ private PlacementContext(World world_1, Direction direction_1, boolean sneakPlace, ItemStack itemStack_1, BlockHitResult hitres) {
+ super(world_1, null, Hand.MAIN_HAND, itemStack_1, hitres);
+ this.facing = direction_1;
+ this.sneakPlace = sneakPlace;
+ }
+
+ @Override
+ public BlockPos getBlockPos() {
+ return this.hit.getBlockPos();
+ }
+
+ @Override
+ public Direction getPlayerLookDirection() {
+ return facing.getOpposite();
+ }
+
+ @Override
+ public Direction[] getPlacementDirections() {
+ switch(this.facing) {
+ case DOWN:
+ default:
+ return new Direction[]{Direction.DOWN, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.UP};
+ case UP:
+ return new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST};
+ case NORTH:
+ return new Direction[]{Direction.DOWN, Direction.NORTH, Direction.EAST, Direction.WEST, Direction.UP, Direction.SOUTH};
+ case SOUTH:
+ return new Direction[]{Direction.DOWN, Direction.SOUTH, Direction.EAST, Direction.WEST, Direction.UP, Direction.NORTH};
+ case WEST:
+ return new Direction[]{Direction.DOWN, Direction.WEST, Direction.SOUTH, Direction.UP, Direction.NORTH, Direction.EAST};
+ case EAST:
+ return new Direction[]{Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.UP, Direction.NORTH, Direction.WEST};
+ }
+ }
+
+ @Override
+ public Direction getPlayerFacing() {
+ return this.facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.facing;
+ }
+
+ @Override
+ public boolean isPlayerSneaking() {
+ return sneakPlace;
+ }
+
+ @Override
+ public float getPlayerYaw() {
+ return (float)(this.facing.getHorizontal() * 90);
+ }
+ }
}
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 9117b02e..9845e712 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -4,35 +4,34 @@ import carpet.fakes.MobEntityInterface;
import carpet.script.exception.InternalExpressionException;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import net.minecraft.client.network.packet.EntityVelocityUpdateS2CPacket;
+import net.minecraft.command.EntitySelector;
+import net.minecraft.command.EntitySelectorReader;
+import net.minecraft.command.arguments.NbtPathArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.ItemEntity;
import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.ai.goal.GoToWalkTargetGoal;
+import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectInstance;
-import net.minecraft.nbt.Tag;
-import net.minecraft.server.command.ServerCommandSource;
-import net.minecraft.command.EntitySelector;
-import net.minecraft.command.EntitySelectorReader;
-import net.minecraft.command.arguments.NbtPathArgumentType;
-import net.minecraft.entity.mob.MobEntityWithAi; // EntityCreature;
-import net.minecraft.entity.mob.MobEntity; //LivingEntity;
-import net.minecraft.entity.ai.goal.Goal;
-import net.minecraft.entity.ai.goal.GoToWalkTargetGoal;
+import net.minecraft.entity.mob.MobEntity;
+import net.minecraft.entity.mob.MobEntityWithAi;
import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
-import net.minecraft.client.network.packet.EntityVelocityUpdateS2CPacket;
-import net.minecraft.potion.Potion;
+import net.minecraft.nbt.Tag;
import net.minecraft.predicate.entity.EntityPredicates;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
-import net.minecraft.text.LiteralText;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
@@ -64,7 +63,7 @@ public class EntityValue extends Value
{
return entitySelector.getEntities(source);
}
- entitySelector = new EntitySelectorReader(new StringReader(selector), true).build();
+ entitySelector = new EntitySelectorReader(new StringReader(selector), true).read();
selectorCache.put(selector, entitySelector);
return entitySelector.getEntities(source);
}
@@ -104,6 +103,21 @@ public class EntityValue extends Value
@Override
public Value in(Value v)
{
+ if (v instanceof ListValue)
+ {
+ List values = ((ListValue) v).getItems();
+ String what = values.get(0).getString();
+ Value arg = null;
+ if (values.size() == 2)
+ {
+ arg = values.get(1);
+ }
+ else if (values.size() > 2)
+ {
+ arg = ListValue.wrap(values.subList(1,values.size()));
+ }
+ return this.get(what, arg);
+ }
String what = v.getString();
return this.get(what, null);
}
@@ -139,6 +153,7 @@ public class EntityValue extends Value
put("feet", EquipmentSlot.FEET);
}};
private static Map> featureAccessors = new HashMap>() {{
+ //put("test", (e, a) -> a == null ? Value.NULL : new StringValue(a.getString()));
put("removed", (entity, arg) -> new NumericValue(entity.removed));
put("uuid",(e, a) -> new StringValue(e.getUuidAsString()));
put("id",(e, a) -> new NumericValue(e.getEntityId()));
@@ -273,19 +288,24 @@ public class EntityValue extends Value
if (where == null)
throw new InternalExpressionException("Unknown inventory slot: "+a.getString());
if (e instanceof LivingEntity)
- {
- ItemStack itemstack = ((LivingEntity)e).getEquippedStack(where);
- if (!itemstack.isEmpty())
- {
- return ListValue.of(
- new StringValue(Registry.ITEM.getId(itemstack.getItem()).getPath()),
- new NumericValue(itemstack.getCount()),
- new StringValue(itemstack.toTag(new CompoundTag()).toString())
- );
- }
- }
+ return ListValue.fromItemStack(((LivingEntity)e).getEquippedStack(where));
return Value.NULL;
+ });
+ put("selected_slot", (e, a) -> {
+ if (e instanceof PlayerEntity)
+ return new NumericValue(((PlayerEntity) e).inventory.selectedSlot);
+ return null;
+ });
+
+ put("facing", (e, a) -> {
+ int index = 0;
+ if (a != null)
+ index = (6+(int)NumericValue.asNumber(a).getLong())%6;
+ if (index < 0 || index > 5)
+ throw new InternalExpressionException("facing order should be between -6 and 5");
+
+ return new StringValue(Direction.getEntityFacingOrder(e)[index].asString());
});
put("nbt",(e, a) -> {
diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java
index 2f1af01e..be416f18 100644
--- a/src/main/java/carpet/script/value/LazyListValue.java
+++ b/src/main/java/carpet/script/value/LazyListValue.java
@@ -100,4 +100,10 @@ public abstract class LazyListValue extends ListValue implements Iterator
throw new InternalExpressionException("Cannot add to iterators");
}
+ @Override
+ public boolean equals(final Value o)
+ {
+ return false;
+ }
+
}
diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java
index e8cfe02f..fefb8a7f 100644
--- a/src/main/java/carpet/script/value/ListValue.java
+++ b/src/main/java/carpet/script/value/ListValue.java
@@ -1,6 +1,9 @@
package carpet.script.value;
import carpet.script.exception.InternalExpressionException;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.util.registry.Registry;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,6 +47,19 @@ public class ListValue extends Value
items = new ArrayList<>();
items.addAll(list);
}
+
+ public static Value fromItemStack(ItemStack itemstack)
+ {
+ if (itemstack == null || itemstack.isEmpty())
+ return Value.NULL;
+ return ListValue.of(
+ new StringValue(Registry.ITEM.getId(itemstack.getItem()).getPath()),
+ new NumericValue(itemstack.getCount()),
+ new NBTSerializableValue(itemstack)
+ );
+ }
+
+
public static ListValue wrap(List list)
{
ListValue created = new ListValue();
@@ -187,25 +203,33 @@ public class ListValue extends Value
ListValue ol = (ListValue)o;
int this_size = this.getItems().size();
int o_size = ol.getItems().size();
- if (this_size != o_size)
- {
- return this_size - o_size;
- }
- if (this_size == 0)
- {
- return 0;
- }
+ if (this_size != o_size) return this_size - o_size;
+ if (this_size == 0) return 0;
for (int i = 0; i < this_size; i++)
{
int res = this.items.get(i).compareTo(ol.items.get(i));
- if (res != 0)
- {
- return res;
- }
+ if (res != 0) return res;
}
return 0;
}
- return super.compareTo(o);
+ return getString().compareTo(o.getString());
+ }
+
+ @Override
+ public boolean equals(final Value o)
+ {
+ if (o instanceof ListValue)
+ {
+ ListValue ol = (ListValue)o;
+ int this_size = this.getItems().size();
+ int o_size = ol.getItems().size();
+ if (this_size != o_size) return false;
+ if (this_size == 0) return true;
+ for (int i = 0; i < this_size; i++)
+ if (!this.items.get(i).equals(ol.items.get(i))) return false;
+ return true;
+ }
+ return false;
}
public List getItems()
diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java
new file mode 100644
index 00000000..14d72f80
--- /dev/null
+++ b/src/main/java/carpet/script/value/NBTSerializableValue.java
@@ -0,0 +1,198 @@
+package carpet.script.value;
+
+import carpet.script.CarpetContext;
+import carpet.script.LazyValue;
+import carpet.script.exception.InternalExpressionException;
+import com.mojang.brigadier.StringReader;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import net.minecraft.block.entity.HopperBlockEntity;
+import net.minecraft.command.arguments.ItemStackArgument;
+import net.minecraft.command.arguments.ItemStringReader;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.passive.VillagerEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.inventory.Inventory;
+import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.StringNbtReader;
+import net.minecraft.util.math.BlockPos;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class NBTSerializableValue extends Value
+{
+ private String nbtString = null;
+ private CompoundTag nbtTag = null;
+ private Supplier nbtSupplier = null;
+
+ public NBTSerializableValue(ItemStack stack)
+ {
+ nbtSupplier = () -> stack.hasTag() ? stack.getTag(): new CompoundTag();
+ }
+
+ public NBTSerializableValue(String nbtString)
+ {
+ nbtSupplier = () ->
+ {
+ try
+ {
+ return (new StringNbtReader(new StringReader(nbtString))).parseCompoundTag();
+ }
+ catch (CommandSyntaxException e)
+ {
+ throw new InternalExpressionException("Incorrect nbt data: "+nbtString);
+ }
+ };
+ }
+
+ public NBTSerializableValue(CompoundTag tag)
+ {
+ nbtTag = tag;
+ }
+
+ public static InventoryLocator locateInventory(CarpetContext c, List params, int offset)
+ {
+ try
+ {
+ Inventory inv = null;
+ Value v1 = params.get(0 + offset).evalValue(c);
+ if (v1 instanceof EntityValue)
+ {
+ Entity e = ((EntityValue) v1).getEntity();
+ if (e instanceof PlayerEntity) inv = ((PlayerEntity) e).inventory;
+ else if (e instanceof Inventory) inv = (Inventory) e;
+ else if (e instanceof VillagerEntity) inv = ((VillagerEntity) e).getInventory();
+
+ if (inv == null)
+ return null;
+
+ return new InventoryLocator(e, e.getBlockPos(), inv, offset + 1);
+ }
+ else if (v1 instanceof BlockValue)
+ {
+ BlockPos pos = ((BlockValue) v1).getPos();
+ if (pos == null)
+ throw new InternalExpressionException("Block to access inventory needs to be positioned in the world");
+ inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos);
+ if (inv == null)
+ return null;
+ return new InventoryLocator(pos, pos, inv, offset + 1);
+ }
+ else if (v1 instanceof ListValue)
+ {
+ List args = ((ListValue) v1).getItems();
+ BlockPos pos = new BlockPos(
+ NumericValue.asNumber(args.get(0)).getDouble(),
+ NumericValue.asNumber(args.get(1)).getDouble(),
+ NumericValue.asNumber(args.get(2)).getDouble());
+ inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos);
+ if (inv == null)
+ return null;
+ return new InventoryLocator(pos, pos, inv, offset + 1);
+ }
+ BlockPos pos = new BlockPos(
+ NumericValue.asNumber(v1).getDouble(),
+ NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(),
+ NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble());
+ inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos);
+ if (inv == null)
+ return null;
+ return new InventoryLocator(pos, pos, inv, offset + 3);
+ }
+ catch (IndexOutOfBoundsException e)
+ {
+ throw new InternalExpressionException("Inventory should be defined either by three coordinates, a block value, or an entity");
+ }
+ }
+
+ private static Map itemCache = new HashMap<>();
+
+ public static ItemStackArgument parseItem(String itemString)
+ {
+ return parseItem(itemString, null);
+ }
+
+ public static ItemStackArgument parseItem(String itemString, CompoundTag customTag)
+ {
+ try
+ {
+ ItemStackArgument res = itemCache.get(itemString);
+ if (res != null)
+ if (customTag == null)
+ return res;
+ else
+ return new ItemStackArgument(res.getItem(), customTag);
+
+ ItemStringReader parser = (new ItemStringReader(new StringReader(itemString), false)).consume();
+ res = new ItemStackArgument(parser.getItem(), parser.getTag());
+ itemCache.put(itemString, res);
+ if (itemCache.size()>64000)
+ itemCache.clear();
+ if (customTag == null)
+ return res;
+ else
+ return new ItemStackArgument(res.getItem(), customTag);
+ }
+ catch (CommandSyntaxException e)
+ {
+ throw new InternalExpressionException("Incorrect item: "+itemString);
+ }
+ }
+
+ public static int validateSlot(int slot, Inventory inv)
+ {
+ int invSize = inv.getInvSize();
+ if (slot < 0)
+ slot = invSize + slot;
+ if (slot < 0 || slot >= invSize)
+ return inv.getInvSize(); // outside of inventory
+ return slot;
+ }
+
+ public CompoundTag getTag()
+ {
+ if (nbtTag == null)
+ nbtTag = nbtSupplier.get();
+ return nbtTag;
+ }
+
+ @Override
+ public boolean equals(final Value o)
+ {
+ if (o instanceof NBTSerializableValue)
+ return getTag().equals(((NBTSerializableValue) o).getTag());
+ return super.equals(o);
+ }
+
+ @Override
+ public String getString()
+ {
+ if (nbtString == null)
+ nbtString = getTag().toString();
+ return nbtString;
+ }
+
+ @Override
+ public boolean getBoolean()
+ {
+ return true;
+ }
+
+ public static class InventoryLocator
+ {
+ public Object owner;
+ public BlockPos position;
+ public Inventory inventory;
+ public int offset;
+ InventoryLocator(Object owner, BlockPos pos, Inventory i, int o)
+ {
+ this.owner = owner;
+ position = pos;
+ inventory = i;
+ offset = o;
+ }
+ }
+}
diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java
index 2a87b334..ec7bc9b2 100644
--- a/src/main/java/carpet/script/value/NumericValue.java
+++ b/src/main/java/carpet/script/value/NumericValue.java
@@ -116,7 +116,7 @@ public class NumericValue extends Value
{
return value.compareTo(((NumericValue) o).getDouble());
}
- return super.compareTo(o);
+ return getString().compareTo(o.getString());
}
@Override
public boolean equals(Value o)
diff --git a/src/main/java/carpet/settings/CarpetSettings.java b/src/main/java/carpet/settings/CarpetSettings.java
index 3e2c00b7..f99044b1 100644
--- a/src/main/java/carpet/settings/CarpetSettings.java
+++ b/src/main/java/carpet/settings/CarpetSettings.java
@@ -25,7 +25,7 @@ import static carpet.settings.RuleCategory.SURVIVAL;
public class CarpetSettings
{
- public static final String carpetVersion = "v19_06_24alpha";
+ public static final String carpetVersion = "v190627";
public static final Logger LOG = LogManager.getLogger();
public static boolean skipGenerationChecks = false;
public static boolean impendingFillSkipUpdates = false;
@@ -51,11 +51,7 @@ public class CarpetSettings
@Rule(desc = "Dropping entire stacks works also from on the crafting UI result slot", category = {BUGFIX, SURVIVAL})
public static boolean ctrlQCraftingFix = false;
- @Rule(
- desc = "Parrots don't get of your shoulders until you receive damage",
- category = {SURVIVAL, FEATURE},
- validate = Validator.WIP.class
- )
+ @Rule(desc = "Parrots don't get of your shoulders until you receive proper damage", category = {SURVIVAL, FEATURE})
public static boolean persistentParrots = false;
/*@Rule(
diff --git a/src/main/java/carpet/settings/ParsedRule.java b/src/main/java/carpet/settings/ParsedRule.java
index b0f0073e..13f02aa5 100644
--- a/src/main/java/carpet/settings/ParsedRule.java
+++ b/src/main/java/carpet/settings/ParsedRule.java
@@ -117,7 +117,7 @@ public final class ParsedRule implements Comparable {
if (!value.equals(get()))
{
this.field.set(null, value);
- SettingsManager.notifyRuleChanged(this, stringValue);
+ SettingsManager.notifyRuleChanged(source, this, stringValue);
}
}
catch (IllegalAccessException e)
diff --git a/src/main/java/carpet/settings/Rule.java b/src/main/java/carpet/settings/Rule.java
index b4c5b316..99876938 100644
--- a/src/main/java/carpet/settings/Rule.java
+++ b/src/main/java/carpet/settings/Rule.java
@@ -17,7 +17,7 @@ import java.lang.annotation.Target;
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
-@interface Rule
+public @interface Rule
{
/**
* The rule name, by default the same as the field name
diff --git a/src/main/java/carpet/settings/SettingsManager.java b/src/main/java/carpet/settings/SettingsManager.java
index 601e74a2..5d0cd0b2 100644
--- a/src/main/java/carpet/settings/SettingsManager.java
+++ b/src/main/java/carpet/settings/SettingsManager.java
@@ -7,6 +7,7 @@ import net.minecraft.server.network.ServerPlayerEntity;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.util.TriConsumer;
import java.io.BufferedReader;
import java.io.File;
@@ -25,7 +26,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import java.util.function.BiConsumer;
import java.util.stream.Collectors;
public class SettingsManager
@@ -34,7 +34,7 @@ public class SettingsManager
private static Map> rules = new HashMap<>();
public boolean locked;
private MinecraftServer server;
- private static List, String>> observers = new ArrayList<>();
+ private static List, String>> observers = new ArrayList<>();
public SettingsManager(MinecraftServer server)
{
@@ -53,14 +53,14 @@ public class SettingsManager
}
}
- public static void addRuleObserver(BiConsumer, String> observer)
+ public static void addRuleObserver(TriConsumer, String> observer)
{
observers.add(observer);
}
- static void notifyRuleChanged(ParsedRule> rule, String userTypedValue)
+ static void notifyRuleChanged(ServerCommandSource source, ParsedRule> rule, String userTypedValue)
{
- observers.forEach(observer -> observer.accept(rule, userTypedValue));
+ observers.forEach(observer -> observer.accept(source, rule, userTypedValue));
}
public static Iterable getCategories()
@@ -71,7 +71,7 @@ public class SettingsManager
}
- public ParsedRule getRule(String name)
+ public static ParsedRule> getRule(String name)
{
return rules.get(name);
}
diff --git a/src/main/java/carpet/settings/Validator.java b/src/main/java/carpet/settings/Validator.java
index 98d6f084..66ca79cf 100644
--- a/src/main/java/carpet/settings/Validator.java
+++ b/src/main/java/carpet/settings/Validator.java
@@ -47,7 +47,7 @@ public abstract class Validator
if (source != null)
try
{
- Messenger.m(source, "r Work in progress - limited or no functionality available for "+currentRule.name);
+ Messenger.m(source, "r "+currentRule.name+" is not yet fully implemented - we are still working on it.");
}
catch (NullPointerException ignored) { }
return newValue;
diff --git a/src/main/java/carpet/utils/BlockInfo.java b/src/main/java/carpet/utils/BlockInfo.java
index bfdda0e4..6632ee43 100644
--- a/src/main/java/carpet/utils/BlockInfo.java
+++ b/src/main/java/carpet/utils/BlockInfo.java
@@ -175,7 +175,7 @@ public class BlockInfo
lst.add(Messenger.s(String.format(" - Brightness in: %.2f, above: %.2f", world.getBrightness(pos), world.getBrightness(pos.up()))));
lst.add(Messenger.s(String.format(" - Is opaque: %s", material.isSolid() )));
//lst.add(Messenger.s(String.format(" - Light opacity: %d", state.getOpacity(world,pos))));
- //lst.add(Messenger.s(String.format(" - Blocks light: %s", state.propagatesSkylightDown(world, pos))));
+ lst.add(Messenger.s(String.format(" - Blocks light: %s", state.getMaterial().blocksLight())));
//lst.add(Messenger.s(String.format(" - Emitted light: %d", state.getLightValue())));
//lst.add(Messenger.s(String.format(" - Picks neighbour light value: %s", state.useNeighborBrightness(world, pos))));
lst.add(Messenger.s(""));
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 8e65cc90..9673bf12 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"id": "carpet",
- "version": "0.1",
+ "version": "${version}",
"name": "Carpet Mod in Fabric",
"description": "Carpet made out of fabric",