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",