From a155494d1183835a2d316bce3f808c12e282c5db Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 12 Jan 2021 19:24:16 +0100 Subject: [PATCH 01/87] Allow some exceptions to be handled --- docs/scarpet/api/Auxiliary.md | 15 +++++ docs/scarpet/api/BlocksAndWorldAccess.md | 21 +++++++ docs/scarpet/api/Inventories.md | 12 +++- docs/scarpet/api/Scoreboard.md | 2 + .../language/FunctionsAndControlFlow.md | 42 ++++++++++--- src/main/java/carpet/script/Expression.java | 7 +-- .../java/carpet/script/api/Auxiliary.java | 29 +++++++-- .../java/carpet/script/api/Inventories.java | 24 ++------ .../java/carpet/script/api/Scoreboards.java | 3 +- .../java/carpet/script/api/WorldAccess.java | 26 ++++---- .../script/exception/ExpressionException.java | 21 +++++-- .../InternalExpressionException.java | 19 ++++++ .../exception/ProcessedThrowStatement.java | 20 ++++++ .../script/exception/ThrowStatement.java | 61 ++++++++++++++++++- .../carpet/script/language/ControlFlow.java | 36 +++++++++-- .../carpet/script/utils/ShapeDispatcher.java | 3 +- .../java/carpet/script/value/BlockValue.java | 4 +- .../carpet/script/value/FunctionValue.java | 10 --- .../script/value/NBTSerializableValue.java | 3 +- .../carpet/script/value/ValueConversions.java | 3 +- 20 files changed, 283 insertions(+), 78 deletions(-) create mode 100644 src/main/java/carpet/script/exception/ProcessedThrowStatement.java diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index cede1808..489017bd 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -11,6 +11,8 @@ optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1 Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient` and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. + +Throws `unknown_sound` if sound doesn't exist. ## Particles @@ -21,12 +23,15 @@ of 0, and to all players nearby, but these options can be changed via optional a command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. +Throws `unknown_particle` if particle doesn't exist. + ### `particle_line(name, pos, pos2, density?, player?)` Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. ### `particle_box(name, pos, pos2, density?, player?)` ### `particle_rect` (deprecated) @@ -34,6 +39,8 @@ that player will receive particles. Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. + ## Markers ### `draw_shape(shape, duration, key?, value?, ... )`, @@ -320,6 +327,10 @@ record to the file. Since files are closed after each write, sending multiple li write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the third argument. +Throws: +- `nbt_read_exception`: When failed to read NBT file +- `json_read_exception`: When failed to read JSON file. The `_msg` variable will contain details about the problem +
 write_file('foo', 'shared_text, ['one', 'two']);
 write_file('foo', 'shared_text', 'three\n', 'four\n');
@@ -371,6 +382,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
 
 You can use app data to save non-vanilla information separately from the world and other scripts.
 
+Throws `nbt_read_exception` if failed to read app data.
+
 ### `store_app_data(tag)`
 
 Note:  `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
@@ -448,6 +461,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt
 world-localized block, so not `block('stone')`, or a string representing a dimension like:
  `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
  
+Throws `unknown_dimension` if given a dimension as a String and it can't be found.
+ 
 ### `view_distance()`
 Returns the view distance of the server.
 
diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index a7419d0f..b3ab8745 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -21,6 +21,8 @@ when it was used. Block values passed in various places like `scan` functions, e
 its properties are needed. This means that if the block at the location changes before its queried in the program this 
 might result in getting the later state, which might not be desired. Consider the following example:
 
+Throws `unknown_block` if block from `state` doesn't exist.
+
 
set(10,10,10,'stone');
 scan(10,10,10,0,0,0, b = _);
 set(10,10,10,'air');
@@ -50,6 +52,8 @@ properties that the original source block may have contained.
 
 The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
 
+Throws `unknown_block` if `block` doesn't exist.
+
 
 set(0,5,0,'bedrock')  => bedrock
 set(l(0,5,0), 'bedrock')  => bedrock
@@ -128,6 +132,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l
 Returns true if placement/use was 
 successful, false otherwise.
 
+Throws `unknown_item` if `item` doesn't exist
+
 
 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
@@ -146,6 +152,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
 Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial 
 markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
 
+Throws `unknown_poi_type` if POI type doesn't exist 
+
 ### `set_biome(pos, biome_name, update=true)`
 
 Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
@@ -156,6 +164,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the
 specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
 noise).
 
+Throws `unknown_biome` if biome doesn't exist.
+
 ### `update(pos)`
 
 Causes a block update at position.
@@ -180,6 +190,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic
 considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the 
 programmer to use that nbt to apply it where it belong
 
+Throws `unknown_item` if `tool` doesn't exist.
+
 Here is a sample code that can be used to mine blocks using items in player inventory, without using player context 
 for mining. Obviously, in this case the use of `harvest` would be much more applicable:
 
@@ -271,6 +283,8 @@ back in state definition in various applications where block properties are requ
 
 `block_state` can also accept block names as input, returning block's default state.
 
+Throws `unknown_block` if `block` comes from a String and doesn't exist.
+
 
 set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z)  => {waterlogged: false, half: top, open: false, ...}
 set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half')  => top
@@ -291,6 +305,8 @@ Without arguments, returns list of available tags, with block supplied (either b
 of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong 
 to this tag, and `true` if the block belongs to the tag.
 
+Throws `unknown_block` if `block` comes from a String and doesn't exist
+
 ### `block_data(pos)`
 
 Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently 
@@ -348,6 +364,8 @@ and values < 0, below sea level.
 * `'features'`: list of features that generate in the biome, grouped by generation steps
 * `'structures'`: list of structures that generate in the biome.
 
+Throws `unknown_block` if `block` comes from a String and doesn't exist. 
+
 ### `solid(pos)`
 
 Boolean function, true if the block is solid.
@@ -716,6 +734,7 @@ If structure is not specified, it will return a set of structure names that are
 as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
 should be generated there.
 
+Throws `unknown_structure` if structure doesn't exist.
 
 ### `structures(pos), structures(pos, structure_name)`
 
@@ -749,6 +768,8 @@ structure starts.
 Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the 
 structure may depend on the biome, otherwise the default structure for this type will be generated.
 
+Throws `unknown_structure` if structure doesn't exist.
+
 ### `plop(pos, what)`
 
 Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates. 
diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md
index ebe2ed7b..b77c26e6 100644
--- a/docs/scarpet/api/Inventories.md
+++ b/docs/scarpet/api/Inventories.md
@@ -28,14 +28,16 @@ an inventory, all API functions 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. 
 
-### item_list(tag?)
+### `item_list(tag?)`
 
 With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid.
 
-### item_tags(item, tag?)
+### `item_tags(item, tag?)`
 
 Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag
 
+Throws `unknown_item` if item doesn't exist.
+
 ### `stack_limit(item)`
 
 Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), 
@@ -43,6 +45,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function
 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.
 
+Throws `unknown_item` if item doesn't exist.
+
 
 stack_limit('wooden_axe') => 1
 stack_limit('ender_pearl') => 16
@@ -53,6 +57,8 @@ stack_limit('stone') => 64
 
 Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`.
 
+Throws `unknown_item` if item doesn't exist.
+
 
 item_category('wooden_axe') => tools
 item_category('ender_pearl') => misc
@@ -175,6 +181,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo
 inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
 
+Throws `unknown_item` if item doesn't exist. + ### `inventory_remove(inventory, item, amount?)` Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index 826c5328..9d152098 100644 --- a/docs/scarpet/api/Scoreboard.md +++ b/docs/scarpet/api/Scoreboard.md @@ -14,6 +14,8 @@ Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'d If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. If the objective was added, returns `true`. If nothing is affected, returns `null` +Throws `unknown_criterion` if criterion doesn't exist. +
 scoreboard_add('counter')
 scoreboard_add('lvl','level')
diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index 3d11e8bf..f44ffcbc 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -234,15 +234,41 @@ returns everywhere, but it would often lead to a messy code.
 
 It terminates entire program passing `expr` as the result of the program execution, or null if omitted.
 
-### `try(expr, catch_expr(_)?) ... throw(value?)`
+### `try(expr, id_filter?, catch_expr(_, _msg)?)`
 
-`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 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.
+`try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, 
+it being produced by `throw` or a catchable exception is thrown by Carpet. In that case the `catch_expr` is evaluated with 
+`_` set to the id of the exception and `_msg` set to the exception message. If `id_filter` is present, only exceptions
+with an id equal to that value will be catched, and any other exception will continue up the stack. It is recommended to filter
+the exceptions to catch to be able to distinguish from an exception that is known that may happen from one that shouldn't.
+This mechanic accepts skipping catch expression - then try returns `null`. 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.
+
+The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items,
+blocks, biomes or dimensions from registries, that may have been modified by resourcepacks or mods, or when an error that
+is out of the scope of the programmer occurs, like problems when reading a file.
+
+In this documentation, those are documented in at least most of the functions that throw them, and the current list of exceptions
+is the following:
+- `unknown_item`: Happens when a specified item doesn't exist
+- `unknown_block`: Happens when a specified block doesn't exist
+- `unknown_biome`: Happens when a specified biome doesn't exist
+- `unknown_sound`: Happens when a specified sound doesn't exist
+- `unknown_particle`: Happens when a specified particle doesn't exist
+- `unknown_poi_type`: Happens when a specified POI type doesn't exist
+- `unknown_dimension`: Happens when a specified dimension doesn't exist
+- `unknown_structure`: Happens when a specified structure doesn't exist
+- `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist
+- `nbt_read_exception`: Happens when there is an exception while reading an NBT file
+- `json_read_exception`: Happens when there is an exception while reading a JSON file
+
+### `throw(id?, message?)`
+
+Throws an exception that can be catched in a `try` block (see above). If ran without arguments, it will pass `null`
+as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is 
+unhandled.
 
 ### `if(cond, expr, cond?, expr?, ..., default?)`
 
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index ad9eb7f5..cb95e860 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -19,7 +19,6 @@ import carpet.script.exception.ExpressionException;
 import carpet.script.exception.InternalExpressionException;
 import carpet.script.exception.ResolvedException;
 import carpet.script.exception.ReturnStatement;
-import carpet.script.exception.ThrowStatement;
 import carpet.script.language.Arithmetic;
 import carpet.script.language.ControlFlow;
 import carpet.script.language.DataStructures;
@@ -232,7 +231,7 @@ public class Expression
         if (exc instanceof ExitStatement)
             return exc;
         if (exc instanceof InternalExpressionException)
-            return new ExpressionException(c, e, token, exc.getMessage(), ((InternalExpressionException) exc).stack);
+            return ((InternalExpressionException) exc).promote(c, e, token);
         if (exc instanceof ArithmeticException)
             return new ExpressionException(c, e, token, "Your math is wrong, "+exc.getMessage());
         if (exc instanceof ResolvedException)
@@ -624,9 +623,9 @@ public class Expression
         {
             return exprProvider.get().evalValue(c, expectedType);
         }
-        catch (ContinueStatement | BreakStatement | ReturnStatement | ThrowStatement exc)
+        catch (ContinueStatement | BreakStatement | ReturnStatement exc)
         {
-            throw new ExpressionException(c, this, "Control flow functions, like continue, break, throw or return, should only be used in loops, try blocks, and functions respectively.");
+            throw new ExpressionException(c, this, "Control flow functions, like continue, break or return, should only be used in loops, and functions respectively.");
         }
         catch (ExitStatement exit)
         {
diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index a0268cc7..74d152fe 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -19,6 +19,7 @@ import carpet.script.argument.FunctionArgument;
 import carpet.script.argument.Vector3Argument;
 import carpet.script.exception.ExitStatement;
 import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
 import carpet.script.utils.FixedCommandSource;
 import carpet.script.utils.ScarpetJsonDeserializer;
 import carpet.script.utils.ShapeDispatcher;
@@ -62,6 +63,7 @@ import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.InvalidIdentifierException;
+import net.minecraft.util.crash.CrashException;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.ChunkPos;
 import net.minecraft.util.math.EulerAngle;
@@ -130,7 +132,7 @@ public class Auxiliary {
             Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString());
             Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
             if (Registry.SOUND_EVENT.get(soundName) == null)
-                throw new InternalExpressionException("No such sound: "+soundName.getPath());
+                throw new ThrowStatement("No such sound: "+soundName.getPath(), ThrowStatement.UNKNOWN_SOUND);
             float volume = 1.0F;
             float pitch = 1.0F;
             SoundCategory mixer = SoundCategory.MASTER;
@@ -820,7 +822,15 @@ public class Auxiliary {
             Value retVal;
             if (fdesc.getMiddle().equals("nbt"))
             {
-                Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
+                Tag state;
+                try
+                {
+                    state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
+                } catch (CrashException e)
+                {
+                    throw new ThrowStatement("Couldn't read NBT data", ThrowStatement.NBT_READ_EXCEPTION); 
+                }
+
                 if (state == null) return LazyValue.NULL;
                 retVal = new NBTSerializableValue(state);
             }
@@ -833,10 +843,10 @@ public class Auxiliary {
                 }
                 catch (JsonParseException e)
                 {
-                    Throwable exception = e;
+                    Throwable exc = e;
                     if(e.getCause() != null)
-                        exception = e.getCause();
-                    throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage());
+                        exc = e.getCause();
+                    throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), ThrowStatement.JSON_READ_EXCEPTION);
                 }
                 Value parsedJson = gson.fromJson(json, Value.class);
                 if (parsedJson == null)
@@ -924,7 +934,14 @@ public class Auxiliary {
                     shared = lv.get(1).evalValue(c).getBoolean();
                 }
             }
-            Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
+            Tag state;
+            try
+            {
+                state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
+            } catch (CrashException e)
+            {
+                throw new ThrowStatement("Failed to read App data", ThrowStatement.NBT_READ_EXCEPTION);
+            }
             if (state == null)
                 return (cc, tt) -> Value.NULL;
             Value retVal = new NBTSerializableValue(state);
diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java
index fb4983ae..713b7ac2 100644
--- a/src/main/java/carpet/script/api/Inventories.java
+++ b/src/main/java/carpet/script/api/Inventories.java
@@ -6,6 +6,7 @@ import carpet.script.CarpetContext;
 import carpet.script.Expression;
 import carpet.script.LazyValue;
 import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
 import carpet.script.value.ListValue;
 import carpet.script.value.NBTSerializableValue;
 import carpet.script.value.NullValue;
@@ -119,14 +120,7 @@ public class Inventories {
                 }
             }
             List> recipes;
-            try
-            {
-                recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
-            }
-            catch (InvalidIdentifierException ignored)
-            {
-                return LazyValue.NULL;
-            }
+            recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
             if (recipes.isEmpty())
                 return LazyValue.NULL;
             List recipesOutput = new ArrayList<>();
@@ -197,17 +191,9 @@ public class Inventories {
         {
             String itemStr = lv.get(0).evalValue(c).getString();
             Item item;
-            try
-            {
-                Identifier id = new Identifier(itemStr);
-                item = Registry.ITEM.get(id);
-                if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air"))
-                    throw new InvalidIdentifierException("boo");
-            }
-            catch (InvalidIdentifierException ignored)
-            {
-                throw new InternalExpressionException("Incorrect item: "+itemStr);
-            }
+            Identifier id = new Identifier(itemStr);
+            item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, ThrowStatement.UNKNOWN_ITEM));
+
             if (!item.hasRecipeRemainder()) return LazyValue.NULL;
             Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder())));
             return (_c, _t ) -> ret;
diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java
index d7cdf1e8..73097127 100644
--- a/src/main/java/carpet/script/api/Scoreboards.java
+++ b/src/main/java/carpet/script/api/Scoreboards.java
@@ -6,6 +6,7 @@ import carpet.script.CarpetContext;
 import carpet.script.Expression;
 import carpet.script.LazyValue;
 import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
 import carpet.script.value.EntityValue;
 import carpet.script.value.ListValue;
 import carpet.script.value.NullValue;
@@ -131,7 +132,7 @@ public class Scoreboards {
                 criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null);
                 if (criterion==null)
                 {
-                    throw new InternalExpressionException("Unknown scoreboard criterion: "+critetionName);
+                    throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, ThrowStatement.UNKNOWN_CRITERION);
                 }
             }
 
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index d15da361..eee5ecd4 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -19,6 +19,7 @@ import carpet.script.LazyValue;
 import carpet.script.argument.BlockArgument;
 import carpet.script.argument.Vector3Argument;
 import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
 import carpet.script.utils.BiomeInfo;
 import carpet.script.utils.WorldTools;
 import carpet.script.value.BlockValue;
@@ -336,8 +337,8 @@ public class WorldAccess {
                 String poiType = lv.get(locator.offset+1).evalValue(c).getString().toLowerCase(Locale.ROOT);
                 if (!"any".equals(poiType))
                 {
-                    PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiType));
-                    if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiType)) return LazyValue.NULL;
+                    PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType))
+                            .orElseThrow(() -> new ThrowStatement("Unknown POI type", ThrowStatement.UNKNOWN_POI_TYPE));
                     condition = (tt) -> tt == type;
                 }
                 if (locator.offset + 2 < lv.size())
@@ -391,9 +392,8 @@ public class WorldAccess {
                 return LazyValue.FALSE;
             }
             String poiTypeString = poi.getString().toLowerCase(Locale.ROOT);
-            PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiTypeString));
-            // solving lack of null with defaulted registries
-            if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiTypeString)) throw new InternalExpressionException("Unknown POI type: "+poiTypeString);
+            PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString))
+            		.orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, ThrowStatement.UNKNOWN_POI_TYPE));
             int occupancy = 0;
             if (locator.offset + 1 < lv.size())
             {
@@ -872,9 +872,8 @@ public class WorldAccess {
                 {
                     playerBreak = true;
                     String itemString = val.getString();
-                    item = Registry.ITEM.get(new Identifier(itemString));
-                    if (item == Items.AIR && !itemString.equals("air"))
-                        throw new InternalExpressionException("Incorrect item: " + itemString);
+                    item = Registry.ITEM.getOrEmpty(new Identifier(itemString))
+                            .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, ThrowStatement.UNKNOWN_ITEM));
                 }
             }
             CompoundTag tag = null;
@@ -1180,9 +1179,8 @@ public class WorldAccess {
                 throw new InternalExpressionException("'set_biome' needs a biome name as an argument");
             String biomeName = lv.get(locator.offset+0).evalValue(c).getString();
             // from locatebiome command code
-            Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(biomeName));
-            if (biome == null)
-                throw new InternalExpressionException("Unknown biome: "+biomeName);
+            Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName))
+                .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, ThrowStatement.UNKNOWN_BIOME));
             boolean doImmediateUpdate = true;
             if (lv.size() > locator.offset+1)
             {
@@ -1251,8 +1249,8 @@ public class WorldAccess {
                 if (!(requested instanceof NullValue))
                 {
                     String reqString = requested.getString();
-                    structure = Registry.STRUCTURE_FEATURE.get(new Identifier(reqString));
-                    if (structure == null) throw new InternalExpressionException("Unknown structure: " + reqString);
+                    structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString))
+                            .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, ThrowStatement.UNKNOWN_STRUCTURE));
                 }
                 if (lv.size() > locator.offset+1)
                 {
@@ -1332,7 +1330,7 @@ public class WorldAccess {
                 throw new InternalExpressionException("'set_structure requires at least position and a structure name");
             String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT);
             ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos);
-            if (configuredStructure == null) throw new InternalExpressionException("Unknown structure: "+structureName);
+            if (configuredStructure == null) throw new ThrowStatement("Unknown structure: "+structureName, ThrowStatement.UNKNOWN_STRUCTURE);
             // good 'ol pointer
             Value[] result = new Value[]{Value.NULL};
             // technically a world modification. Even if we could let it slide, we will still park it
diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index 34d34c96..ec72fccb 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -8,28 +8,32 @@ import carpet.script.value.FunctionValue;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Supplier;
 
 /* The expression evaluators exception class. */
 public class ExpressionException extends RuntimeException implements ResolvedException
 {
     public final Context context;
     public final List stack = new ArrayList<>();
+    private final Supplier lazyStacktrace;
+    private String cachedMessage = null;
 
     public ExpressionException(Context c, Expression e, String message)
     {
-        super(makeMessage(c, e, null, message));
-        context = c;
+        this(c, e, null, message);
     }
 
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
     {
-        super(makeMessage(c, e, t, message));
+        super("Error");
+        lazyStacktrace = () -> makeMessage(c, e, t, message);
         context = c;
     }
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack)
     {
-        super(makeMessage(c, e, t, message));
+        super("Error");
         this.stack.addAll(stack);
+        lazyStacktrace = () -> makeMessage(c, e, t, message); 
         context = c;
     }
 
@@ -68,4 +72,13 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
         }
         return String.join("\n", errorMaker.apply(e, t, message));
     }
+    
+    @Override
+    public String getMessage() {
+        if (cachedMessage == null)
+        {
+        	cachedMessage = lazyStacktrace.get();
+        }
+        return cachedMessage;
+    }
 }
diff --git a/src/main/java/carpet/script/exception/InternalExpressionException.java b/src/main/java/carpet/script/exception/InternalExpressionException.java
index 6efb96c6..1ebab8f3 100644
--- a/src/main/java/carpet/script/exception/InternalExpressionException.java
+++ b/src/main/java/carpet/script/exception/InternalExpressionException.java
@@ -1,5 +1,8 @@
 package carpet.script.exception;
 
+import carpet.script.Context;
+import carpet.script.Expression;
+import carpet.script.Tokenizer;
 import carpet.script.value.FunctionValue;
 
 import java.util.ArrayList;
@@ -13,4 +16,20 @@ public class InternalExpressionException extends RuntimeException
     {
         super(message);
     }
+    
+    /**
+     * 

Promotes this simple exception into one with context and extra information. + * + *

Provides a cleaner way of handling similar exceptions, in this case + * {@link InternalExpressionException} and {@link ThrowStatement} + * @param c Context + * @param e Expression + * @param token Token + * @return The new {@link ExpressionException} (or {@link ProcessedThrowStatement}), + * depending on the implementation. + */ + public ExpressionException promote(Context c, Expression e, Tokenizer.Token token) + { + return new ExpressionException(c, e, token, getMessage(), stack); + } } diff --git a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java new file mode 100644 index 00000000..9e5a9983 --- /dev/null +++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java @@ -0,0 +1,20 @@ +package carpet.script.exception; + +import java.util.List; + +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.Tokenizer.Token; +import carpet.script.value.FunctionValue; +import carpet.script.value.Value; + +public class ProcessedThrowStatement extends ExpressionException { + public final Value exceptionName; + public final String message; + + public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Value exceptionName) { + super(c, e, token, "Unhandled exception: "+message, stack); + this.exceptionName = exceptionName; + this.message = message; + } +} diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index 46c3fe2b..db32a335 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -1,11 +1,68 @@ package carpet.script.exception; +import carpet.script.Context; +import carpet.script.Expression; +import carpet.script.Tokenizer.Token; +import carpet.script.value.StringValue; import carpet.script.value.Value; -public class ThrowStatement extends ExitStatement +public class ThrowStatement extends InternalExpressionException { + public static final String UNKNOWN_ITEM = "unknown_item"; + public static final String UNKNOWN_BLOCK = "unknown_block"; + public static final String UNKNOWN_BIOME = "unknown_biome"; + public static final String UNKNOWN_SOUND = "unknown_sound"; + public static final String UNKNOWN_PARTICLE = "unknown_particle"; + public static final String UNKNOWN_POI_TYPE = "unknown_poi_type"; + public static final String UNKNOWN_DIMENSION = "unknown_dimension"; + public static final String UNKNOWN_STRUCTURE = "unknown_structure"; + public static final String UNKNOWN_CRITERION = "unknown_criterion"; + public static final String NBT_READ_EXCEPTION = "nbt_read_exception"; + public static final String JSON_READ_EXCEPTION = "json_read_exception"; + public final Value retval; + /** + * Creates a throw exception from a value. + * That value will also be used as the message.
+ * To use when throwing from Scarpet's {@code throw} + * @param value The value to pass + */ public ThrowStatement(Value value) { - super(value); + super(value.getString()); + retval = value; + } + + /** + * Creates a throw exception from a value, and + * assigns it a specified message.
+ * To use when throwing from Scarpet's {@code throw} + * function + * @param message The message to display if uncaught + * @param value The value to pass + */ + public ThrowStatement(String message, Value value) + { + super(message); + retval = value; + } + + /** + * Creates a throw exception.
+ * Conveniently creates a value from the {@code value} String + * to be used easily in Java code + * @param message The message to display when not handled + * @param value A String that will be converted + * to a value to pass to {@code catch} + * blocks + */ + public ThrowStatement(String message, String value) + { + super(message); + retval = new StringValue(value); + } + + @Override + public ExpressionException promote(Context c, Expression e, Token token) { + return new ProcessedThrowStatement(c, e, token, getMessage(), stack, retval); } } diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 524cf4ca..309f4f5b 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -5,7 +5,9 @@ import carpet.script.Expression; import carpet.script.LazyValue; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ThrowStatement; +import carpet.script.value.StringValue; import carpet.script.value.Value; public class ControlFlow { @@ -42,7 +44,20 @@ public class ControlFlow { expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); }); - expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); }); + expression.addLazyFunction("throw", -1, (c, t, lv)-> + { + switch (lv.size()) + { + case 0: + throw new ThrowStatement("No further information", Value.NULL); + case 1: + throw new ThrowStatement(lv.get(0).evalValue(c)); + case 2: + throw new ThrowStatement(lv.get(1).evalValue(c).getString(), lv.get(0).evalValue(c)); + default: + throw new InternalExpressionException("throw() can't accept more than 2 parameters"); + } + }); expression.addLazyFunction("try", -1, (c, t, lv) -> { @@ -53,14 +68,27 @@ public class ControlFlow { Value retval = lv.get(0).evalValue(c, t); return (c_, t_) -> retval; } - catch (ThrowStatement ret) + catch (ProcessedThrowStatement 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); + c.setVariable("_", (__c, __t) -> ret.exceptionName.reboundedTo("_")); + LazyValue __msg = c.getVariable("_msg"); + c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); + Value val; + if (lv.size() == 3) + { + if (!ret.exceptionName.equals(lv.get(1).evalValue(c))) + throw ret; + val = lv.get(2).evalValue(c, t); + } + else + { + val = lv.get(1).evalValue(c, t); + } c.setVariable("_",__); + c.setVariable("_msg", __msg); return (c_, t_) -> val; } }); diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 761b8420..ac24c46f 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -4,6 +4,7 @@ import carpet.CarpetSettings; import carpet.network.ServerNetworkHandler; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; import carpet.script.language.Sys; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; @@ -156,7 +157,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new InternalExpressionException("No such particle: "+name); + throw new ThrowStatement("No such particle: "+name, ThrowStatement.UNKNOWN_PARTICLE); } particleCache.put(name, particle); return particle; diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index e883f27f..56846d9b 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -2,6 +2,8 @@ package carpet.script.value; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; + import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.BlockState; @@ -73,7 +75,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new InternalExpressionException("Cannot parse block: "+str); + throw new ThrowStatement("Cannot parse block: "+str, ThrowStatement.UNKNOWN_BLOCK); } public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos) diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index 6517dd66..61e97b18 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -215,7 +215,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction } Value retVal; - boolean rethrow = false; try { retVal = body.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye @@ -228,15 +227,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction { retVal = returnStatement.retval; } - catch (ThrowStatement throwStatement) // might not be really necessary - { - retVal = throwStatement.retval; - rethrow = true; - } - if (rethrow) - { - throw new ThrowStatement(retVal); - } Value otherRetVal = retVal; return (cc, tt) -> otherRetVal; } diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 877300dc..1a790c2b 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -4,6 +4,7 @@ import carpet.fakes.InventoryBearerInterface; import carpet.script.CarpetContext; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; import carpet.script.utils.EquipmentInventory; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -297,7 +298,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new InternalExpressionException("Incorrect item: "+itemString); + throw new ThrowStatement("Incorrect item: "+itemString, ThrowStatement.UNKNOWN_ITEM); } } diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index b3344f40..9fc4e62f 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -2,6 +2,7 @@ package carpet.script.value; import carpet.fakes.BlockPredicateInterface; import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ThrowStatement; import carpet.utils.BlockInfo; import net.minecraft.block.BlockState; import net.minecraft.block.MaterialColor; @@ -158,7 +159,7 @@ public class ValueConversions } } if (dim == null) - throw new InternalExpressionException("Incorrect dimension string: "+dimString); + throw new ThrowStatement("Incorrect dimension string: "+dimString, ThrowStatement.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 86dabe008047059bad3f782d9365894a92e962dc Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 00:40:50 +0100 Subject: [PATCH 02/87] Add exception hierarchy --- .../language/FunctionsAndControlFlow.md | 57 ++++---- .../java/carpet/script/api/Auxiliary.java | 9 +- .../java/carpet/script/api/Inventories.java | 3 +- .../java/carpet/script/api/Scoreboards.java | 3 +- .../java/carpet/script/api/WorldAccess.java | 13 +- .../exception/ProcessedThrowStatement.java | 7 +- .../script/exception/ThrowStatement.java | 41 +++--- .../carpet/script/exception/Throwables.java | 127 ++++++++++++++++++ .../carpet/script/language/ControlFlow.java | 18 ++- .../carpet/script/utils/ShapeDispatcher.java | 3 +- .../java/carpet/script/value/BlockValue.java | 3 +- .../script/value/NBTSerializableValue.java | 3 +- .../carpet/script/value/ValueConversions.java | 3 +- 13 files changed, 216 insertions(+), 74 deletions(-) create mode 100644 src/main/java/carpet/script/exception/Throwables.java diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index f44ffcbc..28a03fb0 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -237,39 +237,50 @@ It terminates entire program passing `expr` as the result of the program executi ### `try(expr, id_filter?, catch_expr(_, _msg)?)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, -it being produced by `throw` or a catchable exception is thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` set to the id of the exception and `_msg` set to the exception message. If `id_filter` is present, only exceptions -with an id equal to that value will be catched, and any other exception will continue up the stack. It is recommended to filter +it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with +`_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions +with an id equal to that value will be caught, and any other exception will continue up the stack. It is recommended to filter the exceptions to catch to be able to distinguish from an exception that is known that may happen from one that shouldn't. This mechanic accepts skipping catch expression - then try returns `null`. 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. +portion of a convoluted call stack and continue program execution. + +A `try` block will not only catch any exception whose id match the `id_filter`, but also any exception that has a parent with +their id being that one, recursively. The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items, -blocks, biomes or dimensions from registries, that may have been modified by resourcepacks or mods, or when an error that -is out of the scope of the programmer occurs, like problems when reading a file. +blocks, biomes or dimensions from registries, that may have been modified by datapacks, resourcepacks or other mods, or when an error that +is out of the scope of the programmer occurs, such as problems when reading files. -In this documentation, those are documented in at least most of the functions that throw them, and the current list of exceptions -is the following: -- `unknown_item`: Happens when a specified item doesn't exist -- `unknown_block`: Happens when a specified block doesn't exist -- `unknown_biome`: Happens when a specified biome doesn't exist -- `unknown_sound`: Happens when a specified sound doesn't exist -- `unknown_particle`: Happens when a specified particle doesn't exist -- `unknown_poi_type`: Happens when a specified POI type doesn't exist -- `unknown_dimension`: Happens when a specified dimension doesn't exist -- `unknown_structure`: Happens when a specified structure doesn't exist -- `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist -- `nbt_read_exception`: Happens when there is an exception while reading an NBT file -- `json_read_exception`: Happens when there is an exception while reading a JSON file +In this documentation, those are documented in at least most of the functions that throw them, and the current exception +hierarchy is the following: +- `exception`: This is the base exception. Every exception has this as its top-level parent + - `value_exception`: This is the parent for any exception that occurs due to a provided value for something not being present in the game + - `unknown_item`: Happens when a specified item doesn't exist + - `unknown_block`: Happens when a specified block doesn't exist + - `unknown_biome`: Happens when a specified biome doesn't exist + - `unknown_sound`: Happens when a specified sound doesn't exist + - `unknown_particle`: Happens when a specified particle doesn't exist + - `unknown_poi_type`: Happens when a specified POI type doesn't exist + - `unknown_dimension`: Happens when a specified dimension doesn't exist + - `unknown_structure`: Happens when a specified structure doesn't exist + - `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist + - `file_read_exception`: This is the parent for any exception that occurs due to an error while reading a file, including appdata load + - `nbt_read_exception`: Happens when there is an exception while reading an NBT file + - `json_read_exception`: Happens when there is an exception while reading a JSON file + - `user_defined_exception`: This is the parent for any exception thrown by the below `throw` function, except for those that explicitly declare + their parent to be one of the above -### `throw(id?, message?)` +### `throw(id?, message?, parent?)` -Throws an exception that can be catched in a `try` block (see above). If ran without arguments, it will pass `null` +Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is unhandled. +By default, the parent of any exception thrown by this function is `user_defined_exception`. +You can specify the `parent` to be either an already existing exception from the list above, in which case your exception +will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from +`exception` > `user_defined_exception` > `parent` > `id` + ### `if(cond, expr, cond?, expr?, ..., default?)` If statement is a function that takes a number of conditions that are evaluated one after another and if any of diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 74d152fe..df5bb8df 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -20,6 +20,7 @@ import carpet.script.argument.Vector3Argument; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.utils.FixedCommandSource; import carpet.script.utils.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; @@ -132,7 +133,7 @@ public class Auxiliary { Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString()); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); if (Registry.SOUND_EVENT.get(soundName) == null) - throw new ThrowStatement("No such sound: "+soundName.getPath(), ThrowStatement.UNKNOWN_SOUND); + throw new ThrowStatement("No such sound: "+soundName.getPath(), Throwables.UNKNOWN_SOUND); float volume = 1.0F; float pitch = 1.0F; SoundCategory mixer = SoundCategory.MASTER; @@ -828,7 +829,7 @@ public class Auxiliary { state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight()); } catch (CrashException e) { - throw new ThrowStatement("Couldn't read NBT data", ThrowStatement.NBT_READ_EXCEPTION); + throw new ThrowStatement("Couldn't read NBT data", Throwables.NBT_READ); } if (state == null) return LazyValue.NULL; @@ -846,7 +847,7 @@ public class Auxiliary { Throwable exc = e; if(e.getCause() != null) exc = e.getCause(); - throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), ThrowStatement.JSON_READ_EXCEPTION); + throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), Throwables.JSON_READ); } Value parsedJson = gson.fromJson(json, Value.class); if (parsedJson == null) @@ -940,7 +941,7 @@ public class Auxiliary { state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); } catch (CrashException e) { - throw new ThrowStatement("Failed to read App data", ThrowStatement.NBT_READ_EXCEPTION); + throw new ThrowStatement("Failed to read App data", Throwables.NBT_READ); } if (state == null) return (cc, tt) -> Value.NULL; diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index 713b7ac2..01fa4ea8 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -7,6 +7,7 @@ import carpet.script.Expression; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.value.ListValue; import carpet.script.value.NBTSerializableValue; import carpet.script.value.NullValue; @@ -192,7 +193,7 @@ public class Inventories { String itemStr = lv.get(0).evalValue(c).getString(); Item item; Identifier id = new Identifier(itemStr); - item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, ThrowStatement.UNKNOWN_ITEM)); + item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, Throwables.UNKNOWN_ITEM)); if (!item.hasRecipeRemainder()) return LazyValue.NULL; Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder()))); diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index 73097127..c58f7c10 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -7,6 +7,7 @@ import carpet.script.Expression; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.value.EntityValue; import carpet.script.value.ListValue; import carpet.script.value.NullValue; @@ -132,7 +133,7 @@ public class Scoreboards { criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null); if (criterion==null) { - throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, ThrowStatement.UNKNOWN_CRITERION); + throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, Throwables.UNKNOWN_CRITERION); } } diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index eee5ecd4..85367839 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -20,6 +20,7 @@ import carpet.script.argument.BlockArgument; import carpet.script.argument.Vector3Argument; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.utils.BiomeInfo; import carpet.script.utils.WorldTools; import carpet.script.value.BlockValue; @@ -338,7 +339,7 @@ public class WorldAccess { if (!"any".equals(poiType)) { PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type", ThrowStatement.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement("Unknown POI type", Throwables.UNKNOWN_POI_TYPE)); condition = (tt) -> tt == type; } if (locator.offset + 2 < lv.size()) @@ -393,7 +394,7 @@ public class WorldAccess { } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, ThrowStatement.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, Throwables.UNKNOWN_POI_TYPE)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { @@ -873,7 +874,7 @@ public class WorldAccess { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.getOrEmpty(new Identifier(itemString)) - .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, ThrowStatement.UNKNOWN_ITEM)); + .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, Throwables.UNKNOWN_ITEM)); } } CompoundTag tag = null; @@ -1180,7 +1181,7 @@ public class WorldAccess { String biomeName = lv.get(locator.offset+0).evalValue(c).getString(); // from locatebiome command code Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName)) - .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, ThrowStatement.UNKNOWN_BIOME)); + .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, Throwables.UNKNOWN_BIOME)); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { @@ -1250,7 +1251,7 @@ public class WorldAccess { { String reqString = requested.getString(); structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString)) - .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, ThrowStatement.UNKNOWN_STRUCTURE)); + .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, Throwables.UNKNOWN_STRUCTURE)); } if (lv.size() > locator.offset+1) { @@ -1330,7 +1331,7 @@ public class WorldAccess { throw new InternalExpressionException("'set_structure requires at least position and a structure name"); String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos); - if (configuredStructure == null) throw new ThrowStatement("Unknown structure: "+structureName, ThrowStatement.UNKNOWN_STRUCTURE); + if (configuredStructure == null) throw new ThrowStatement("Unknown structure: "+structureName, Throwables.UNKNOWN_STRUCTURE); // good 'ol pointer Value[] result = new Value[]{Value.NULL}; // technically a world modification. Even if we could let it slide, we will still park it diff --git a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java index 9e5a9983..003b71b0 100644 --- a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java +++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java @@ -6,15 +6,14 @@ import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer.Token; import carpet.script.value.FunctionValue; -import carpet.script.value.Value; public class ProcessedThrowStatement extends ExpressionException { - public final Value exceptionName; + public final Throwables.Exception exception; public final String message; - public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Value exceptionName) { + public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Throwables.Exception exception) { super(c, e, token, "Unhandled exception: "+message, stack); - this.exceptionName = exceptionName; + this.exception = exception; this.message = message; } } diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index db32a335..b8cb4155 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -3,47 +3,35 @@ package carpet.script.exception; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer.Token; -import carpet.script.value.StringValue; import carpet.script.value.Value; +import static carpet.script.exception.Throwables.Exception; + public class ThrowStatement extends InternalExpressionException { - public static final String UNKNOWN_ITEM = "unknown_item"; - public static final String UNKNOWN_BLOCK = "unknown_block"; - public static final String UNKNOWN_BIOME = "unknown_biome"; - public static final String UNKNOWN_SOUND = "unknown_sound"; - public static final String UNKNOWN_PARTICLE = "unknown_particle"; - public static final String UNKNOWN_POI_TYPE = "unknown_poi_type"; - public static final String UNKNOWN_DIMENSION = "unknown_dimension"; - public static final String UNKNOWN_STRUCTURE = "unknown_structure"; - public static final String UNKNOWN_CRITERION = "unknown_criterion"; - public static final String NBT_READ_EXCEPTION = "nbt_read_exception"; - public static final String JSON_READ_EXCEPTION = "json_read_exception"; - public final Value retval; + private final Exception exception; /** * Creates a throw exception from a value. * That value will also be used as the message.
- * To use when throwing from Scarpet's {@code throw} + * To be used when throwing from Scarpet's {@code throw} function with a single argument * @param value The value to pass */ public ThrowStatement(Value value) { - super(value.getString()); - retval = value; + this(value, value, Value.NULL); } /** - * Creates a throw exception from a value, and - * assigns it a specified message.
- * To use when throwing from Scarpet's {@code throw} - * function + * Creates a throw exception from a value, and assigns it a specified message. + *

To be used when throwing from Scarpet's {@code throw} function * @param message The message to display if uncaught * @param value The value to pass + * @param parent A parent's name */ - public ThrowStatement(String message, Value value) + public ThrowStatement(Value message, Value value, Value parent) { - super(message); - retval = value; + super(message.getString()); + exception = new Exception(value, parent); } /** @@ -54,15 +42,16 @@ public class ThrowStatement extends InternalExpressionException * @param value A String that will be converted * to a value to pass to {@code catch} * blocks + * @param parent This exception's data */ - public ThrowStatement(String message, String value) + public ThrowStatement(String message, Exception exception) { super(message); - retval = new StringValue(value); + this.exception = exception; } @Override public ExpressionException promote(Context c, Expression e, Token token) { - return new ProcessedThrowStatement(c, e, token, getMessage(), stack, retval); + return new ProcessedThrowStatement(c, e, token, getMessage(), stack, exception); } } diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java new file mode 100644 index 00000000..4bdfeebe --- /dev/null +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -0,0 +1,127 @@ +package carpet.script.exception; + +import java.util.HashMap; +import java.util.Map; + +import carpet.script.value.NullValue; +import carpet.script.value.StringValue; +import carpet.script.value.Value; + +/** + * This class contains default Scarpet catchable exception's ids, as well as their inheritance and + * methods to check whether filters are compatible with those. + * + * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for + * {@link ThrowStatement} + */ +public class Throwables { + private static final Map byValue = new HashMap<>(); + public static final Exception EXCEPTION = register("exception", null); + public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); + public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); + public static final Exception UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); + public static final Exception UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); + public static final Exception UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); + public static final Exception UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); + public static final Exception UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); + public static final Exception UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); + public static final Exception UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); + public static final Exception UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); + public static final Exception FILE_READ_EXCEPTION = register("file_read_exception", EXCEPTION); + public static final Exception NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); + public static final Exception JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); + public static final Exception USER_DEFINED = register("user_defined_exception", EXCEPTION); + protected static final Exception ERROR = register("error", null); + + private Throwables() {} + + /** + * Creates an exception and registers it to be used as parent for + * user defined exceptions in Scarpet's throw function. + *

Scarpet exceptions should, in general, have a top-level parent being {@link Throwables#EXCEPTION} + * @param name The name for the exception. Will be converted into a {@link StringValue} + * @param parent The parent of the exception being created, or null if top-level + * @return The created exception + */ + protected static Exception register(String value, Exception parent) + { + Exception exc = new Exception(value, parent); + byValue.put(exc.getValue(), exc); + return exc; + } + + /** + * The data of a {@link ThrowStatement} exception. Supports creating with inheritance and + * checking whether a filter is compatible with them. + */ + public static class Exception { + private final Value value; + private final Exception parent; + private final boolean isError; + + /** + * Creates a new exception, with an optional parent. + *

To be used in Scarpet's throw command + *

Parent will default to {@link Throwables#USER_DEFINED} if provided + * parentValue meets Value#isNull, else it will create a new {@link Exception}, + * with its parent being {@link Throwables#USER_DEFINED}. + * @param name The name/id of the exception + * @param parentValue An optional Value matching the name of a parent exception. + *
Accepts a {@link NullValue}, but not a null + */ + public Exception(Value value, Value parentValue) + { + if (byValue.containsKey(value)) + throw new InternalExpressionException("Exception id can't be the same as existing exception. Use it as parent instead"); + this.value = value; + if (!parentValue.isNull()) + { + Exception possibleParent = byValue.get(parentValue); + parent = possibleParent == null ? new Exception(parentValue, Value.NULL) : possibleParent; + } else + parent = USER_DEFINED; + isError = this.parent == ERROR; + } + + /** + * Creates a new exception. + *

Not suitable for creating exceptions that can't be caught. + * Use an {@link InternalExpressionException} for that + * @param name The exception's name/id + * @param parent The parent exception, or null if no parent is available + */ + protected Exception(String value, Exception parent) + { + this.value = StringValue.of(value); + this.parent = parent; + this.isError = false; + } + + /** + * Checks whether the given filter matches an instance of this exception, including checking equality + * with itself and possible parents. + * @param filter The value to check against + * @return Whether or not the given value matches this exception's hierarchy + */ + public boolean isInstance(Value filter) { + return getValue().equals(filter) || (parent != null && parent.isInstance(filter)); + } + + /** + * Returns the {@link Value} provided when creating this exception + * @return The {@link Value} of this exception + */ + public Value getValue() { + return value; + } + + /** + * Returns whether this exception has been defined as an error. + *

Error exceptions cannot be caught in Scarpet + * @return Whether this exception has been defined as an error + */ + public boolean isError() { + return isError; + } + } +} diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 309f4f5b..076a29eb 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -49,13 +49,15 @@ public class ControlFlow { switch (lv.size()) { case 0: - throw new ThrowStatement("No further information", Value.NULL); + throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); case 2: - throw new ThrowStatement(lv.get(1).evalValue(c).getString(), lv.get(0).evalValue(c)); + throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), Value.NULL); + case 3: + throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), lv.get(2).evalValue(c)); default: - throw new InternalExpressionException("throw() can't accept more than 2 parameters"); + throw new InternalExpressionException("throw() can't accept more than 3 parameters"); } }); @@ -70,17 +72,23 @@ public class ControlFlow { } catch (ProcessedThrowStatement ret) { + if (ret.exception.isError()) + throw ret; if (lv.size() == 1) return (c_, t_) -> Value.NULL; LazyValue __ = c.getVariable("_"); - c.setVariable("_", (__c, __t) -> ret.exceptionName.reboundedTo("_")); + c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); Value val; if (lv.size() == 3) { - if (!ret.exceptionName.equals(lv.get(1).evalValue(c))) + if (!ret.exception.isInstance(lv.get(1).evalValue(c))) + { + c.setVariable("_", __); + c.setVariable("_msg", __msg); throw ret; + } val = lv.get(2).evalValue(c, t); } else diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index ac24c46f..84aab23a 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -5,6 +5,7 @@ import carpet.network.ServerNetworkHandler; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.language.Sys; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; @@ -157,7 +158,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new ThrowStatement("No such particle: "+name, ThrowStatement.UNKNOWN_PARTICLE); + throw new ThrowStatement("No such particle: "+name, Throwables.UNKNOWN_PARTICLE); } particleCache.put(name, particle); return particle; diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index 56846d9b..fe26f57f 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -3,6 +3,7 @@ package carpet.script.value; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -75,7 +76,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new ThrowStatement("Cannot parse block: "+str, ThrowStatement.UNKNOWN_BLOCK); + throw new ThrowStatement("Cannot parse block: "+str, Throwables.UNKNOWN_BLOCK); } public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos) diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 1a790c2b..11595d06 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -5,6 +5,7 @@ import carpet.script.CarpetContext; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.utils.EquipmentInventory; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -298,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new ThrowStatement("Incorrect item: "+itemString, ThrowStatement.UNKNOWN_ITEM); + throw new ThrowStatement("Incorrect item: "+itemString, Throwables.UNKNOWN_ITEM); } } diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index 9fc4e62f..320916ae 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -3,6 +3,7 @@ package carpet.script.value; import carpet.fakes.BlockPredicateInterface; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.utils.BlockInfo; import net.minecraft.block.BlockState; import net.minecraft.block.MaterialColor; @@ -159,7 +160,7 @@ public class ValueConversions } } if (dim == null) - throw new ThrowStatement("Incorrect dimension string: "+dimString, ThrowStatement.UNKNOWN_DIMENSION); + throw new ThrowStatement("Incorrect dimension string: "+dimString, Throwables.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 0b86ac1a8704311fb38573f8356584796e5317c4 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 01:52:55 +0100 Subject: [PATCH 03/87] Add multi-catch to try block --- .../language/FunctionsAndControlFlow.md | 10 ++++--- .../carpet/script/language/ControlFlow.java | 27 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 28a03fb0..8b4d50a3 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -234,13 +234,17 @@ returns everywhere, but it would often lead to a messy code. It terminates entire program passing `expr` as the result of the program execution, or null if omitted. -### `try(expr, id_filter?, catch_expr(_, _msg)?)` +### `try(expr, id_filter?, catch_expr(_, _msg)?, id_filter2?, catch_expr2(_, _msg), ...)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with `_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions -with an id equal to that value will be caught, and any other exception will continue up the stack. It is recommended to filter -the exceptions to catch to be able to distinguish from an exception that is known that may happen from one that shouldn't. +with an id equal to that value will be caught, and any other exception will continue up the stack. You can specify multiple filters +and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script +will try to find an equality with every provided filter in order. Therefore, even if the caught exception matches multiple filters, only +the first matching block will be executed. It is recommended to filter the exceptions to catch to be able to distinguish from an exception +that is known that may happen from one that shouldn't. + This mechanic accepts skipping catch expression - then try returns `null`. This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 076a29eb..cbf5bebd 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -76,20 +76,34 @@ public class ControlFlow { throw ret; if (lv.size() == 1) return (c_, t_) -> Value.NULL; + // This is outside since it is also used in try(expr,catch_expr). LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val; - if (lv.size() == 3) + Value val = null; // This is always assigned, just the compiler doesn't know + if (lv.size() >= 3) { - if (!ret.exception.isInstance(lv.get(1).evalValue(c))) + if (lv.size() % 2 == 0) + throw new InternalExpressionException("Multi-catch block needs a catch expression for every filter"); + int pointer = 1; + boolean handled = false; + while (pointer < lv.size() -1) { - c.setVariable("_", __); + if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) + { + val = lv.get(pointer + 1).evalValue(c, t); + handled = true; + break; + } + pointer += 2; + } + if (!handled) + { + c.setVariable("_", __); c.setVariable("_msg", __msg); throw ret; } - val = lv.get(2).evalValue(c, t); } else { @@ -97,7 +111,8 @@ public class ControlFlow { } c.setVariable("_",__); c.setVariable("_msg", __msg); - return (c_, t_) -> val; + Value retval = val; + return (c_, t_) -> retval; } }); } From 516de37dd294cf6dcee2c36a98bee92821617b59 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 02:16:03 +0100 Subject: [PATCH 04/87] Deprecate try without id and therefore throw without id --- .../language/FunctionsAndControlFlow.md | 23 +++++++++++-------- .../carpet/script/language/ControlFlow.java | 11 ++++++--- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 8b4d50a3..4562b4dd 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -234,27 +234,28 @@ returns everywhere, but it would often lead to a messy code. It terminates entire program passing `expr` as the result of the program execution, or null if omitted. -### `try(expr, id_filter?, catch_expr(_, _msg)?, id_filter2?, catch_expr2(_, _msg), ...)` +### `try(expr, id_filter, catch_expr(_, _msg), id_filter2?, catch_expr2(_, _msg), ...)` `try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` set to the id of the exception and `_msg` set to the exception message. If an `id_filter` is present, only exceptions -with an id equal to that value will be caught, and any other exception will continue up the stack. You can specify multiple filters +`_` set to the id of the exception and `_msg` set to the exception message. Only exceptions with their or their parent's id(recursively) equal +to a provided `id_filter` will be caught, and any other exception will continue up the stack. You can specify multiple filters and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script will try to find an equality with every provided filter in order. Therefore, even if the caught exception matches multiple filters, only the first matching block will be executed. It is recommended to filter the exceptions to catch to be able to distinguish from an exception that is known that may happen from one that shouldn't. -This mechanic accepts skipping catch expression - then try returns `null`. This mechanism allows to terminate large -portion of a convoluted call stack and continue program execution. - -A `try` block will not only catch any exception whose id match the `id_filter`, but also any exception that has a parent with -their id being that one, recursively. +This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items, blocks, biomes or dimensions from registries, that may have been modified by datapacks, resourcepacks or other mods, or when an error that is out of the scope of the programmer occurs, such as problems when reading files. +Deprecated usages: +- `try(expr)`: Filter the exception you are looking for, and provide something as the `catch_expr` (use `null` if you have nothing to provide). + Example replacement: `try(expr, 'exception', null)` +- `try(expr, catch_expr)`: Filter the exception you are looking for. + In this documentation, those are documented in at least most of the functions that throw them, and the current exception hierarchy is the following: - `exception`: This is the base exception. Every exception has this as its top-level parent @@ -274,7 +275,7 @@ hierarchy is the following: - `user_defined_exception`: This is the parent for any exception thrown by the below `throw` function, except for those that explicitly declare their parent to be one of the above -### `throw(id?, message?, parent?)` +### `throw(id, message?, parent?)` Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is @@ -283,7 +284,9 @@ unhandled. By default, the parent of any exception thrown by this function is `user_defined_exception`. You can specify the `parent` to be either an already existing exception from the list above, in which case your exception will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from -`exception` > `user_defined_exception` > `parent` > `id` +`exception` > `user_defined_exception` > `parent` > `id`. + +Deprecated usage: `throw()` (without id): Specify at least the id of your exception ### `if(cond, expr, cond?, expr?, ..., default?)` diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index cbf5bebd..029bf8ac 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -49,6 +49,7 @@ public class ControlFlow { switch (lv.size()) { case 0: + c.host.issueDeprecation("throw() (without id)"); throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); @@ -64,7 +65,7 @@ public class ControlFlow { expression.addLazyFunction("try", -1, (c, t, lv) -> { if (lv.size()==0) - throw new InternalExpressionException("'try' needs at least an expression block"); + throw new InternalExpressionException("'try' needs at least an expression block, a filter, and a catch_epr"); try { Value retval = lv.get(0).evalValue(c, t); @@ -75,13 +76,16 @@ public class ControlFlow { if (ret.exception.isError()) throw ret; if (lv.size() == 1) + { + c.host.issueDeprecation("try(expr) (without filter and catch expression)"); return (c_, t_) -> Value.NULL; - // This is outside since it is also used in try(expr,catch_expr). + } + // This is outside since it is used in try(expr,catch_expr) LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val = null; // This is always assigned, just the compiler doesn't know + Value val = null; // This is always assigned at some point, just the compiler doesn't know if (lv.size() >= 3) { if (lv.size() % 2 == 0) @@ -107,6 +111,7 @@ public class ControlFlow { } else { + c.host.issueDeprecation("try(expr, catch_expr) (without filter)"); val = lv.get(1).evalValue(c, t); } c.setVariable("_",__); From ae70529f2041dd6c0c6f9831450acafd91cad3c8 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 02:50:49 +0100 Subject: [PATCH 05/87] Mention error in docs --- docs/scarpet/language/FunctionsAndControlFlow.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 4562b4dd..79c931a3 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -286,6 +286,8 @@ You can specify the `parent` to be either an already existing exception from the will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from `exception` > `user_defined_exception` > `parent` > `id`. +If you don't want your exception to be handled, you can use `error` as the `parent` to throw an error which cannot be handled. + Deprecated usage: `throw()` (without id): Specify at least the id of your exception ### `if(cond, expr, cond?, expr?, ..., default?)` From 7b1c58d81e9d2c9738b26d06204c38b1174ca39e Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 21 Feb 2021 12:34:44 +0100 Subject: [PATCH 06/87] Minor docs and visibility changes for exceptions --- .../carpet/script/exception/Throwables.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 4bdfeebe..ab6af103 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -12,10 +12,10 @@ import carpet.script.value.Value; * methods to check whether filters are compatible with those. * * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for - * {@link ThrowStatement} + * {@link ThrowStatement} and {@link ProcessedThrowStatement} */ public class Throwables { - private static final Map byValue = new HashMap<>(); + protected static final Map byValue = new HashMap<>(); public static final Exception EXCEPTION = register("exception", null); public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); @@ -39,11 +39,11 @@ public class Throwables { * Creates an exception and registers it to be used as parent for * user defined exceptions in Scarpet's throw function. *

Scarpet exceptions should, in general, have a top-level parent being {@link Throwables#EXCEPTION} - * @param name The name for the exception. Will be converted into a {@link StringValue} + * @param value The value for the exception as a {@link String}. Will be converted into a {@link StringValue} * @param parent The parent of the exception being created, or null if top-level * @return The created exception */ - protected static Exception register(String value, Exception parent) + public static Exception register(String value, Exception parent) { Exception exc = new Exception(value, parent); byValue.put(exc.getValue(), exc); @@ -65,14 +65,14 @@ public class Throwables { *

Parent will default to {@link Throwables#USER_DEFINED} if provided * parentValue meets Value#isNull, else it will create a new {@link Exception}, * with its parent being {@link Throwables#USER_DEFINED}. - * @param name The name/id of the exception - * @param parentValue An optional Value matching the name of a parent exception. - *
Accepts a {@link NullValue}, but not a null + * @param value The {@link Value} of the exception + * @param parentValue An optional {@link Value} matching the name of a parent exception. + *
Accepts a {@link NullValue}, but not a {@code null} */ public Exception(Value value, Value parentValue) { if (byValue.containsKey(value)) - throw new InternalExpressionException("Exception id can't be the same as existing exception. Use it as parent instead"); + throw new InternalExpressionException("Exception value can't be the same as existing exception. Use it as parent instead"); this.value = value; if (!parentValue.isNull()) { @@ -87,7 +87,7 @@ public class Throwables { * Creates a new exception. *

Not suitable for creating exceptions that can't be caught. * Use an {@link InternalExpressionException} for that - * @param name The exception's name/id + * @param value The exception's value as a {@link String} * @param parent The parent exception, or null if no parent is available */ protected Exception(String value, Exception parent) @@ -100,7 +100,7 @@ public class Throwables { /** * Checks whether the given filter matches an instance of this exception, including checking equality * with itself and possible parents. - * @param filter The value to check against + * @param filter The {@link Value} to check against * @return Whether or not the given value matches this exception's hierarchy */ public boolean isInstance(Value filter) { From b649f5c83088de72c165d2ed42bf4ae5026e80b3 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 09:37:21 +0100 Subject: [PATCH 07/87] Update camera.sc --- .../resources/assets/carpet/scripts/camera.sc | 56 +++++++++++++++---- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 7d146cdd..8f21ced5 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -1,4 +1,4 @@ -__command() -> +_command() -> ( print('camera: scarpet app.'); print('-------------------'); @@ -11,7 +11,7 @@ __command() -> print(' "/camera place_player - Move player to the selected point'); print(' "/camera move" - Move the selected point to players location'); print(' "/camera duration " - Set new selected path duration'); - print(' "/camera split_point>" - Split selected path in half.'); + print(' "/camera split_point" - Split selected path in half.'); print(' "/camera delete_point" - Remove current key point'); print(' "/camera trim_path" - Remove all key points from selected up'); print(''); @@ -57,6 +57,47 @@ __command() -> print(''); null ); +__config() ->{ + 'commands'->{ + ''->'_command', + ''->'_call', + 'add '->'add', + 'prepend '->'prepend', + 'duration ', + 'save_as '->'save_as', + 'load '->'load', + 'interpolation '->['__interpolation',true], + 'interpolation gauss'->['__interpolation','gauss',true], + 'interpolation gauss '->_(float)->(__interpolation('gauss_'+str(float),true)), + 'repeat '->'repeat', + 'stretch '->'stretch' + }, + 'arguments'->{ + 'seconds'->{'type'->'int','suggest'->[]}, + 'last_delay'->{'type'->'int','suggest'->[]}, + 'name'->{'type'->'string','suggest'->[]}, + 'interpolation'->{'type'->'term','options'->['linear','catmull_rom']} + 'factor'->{'type'->'int','min'->25,'max'->'400'}, + 'command'->{'type'->'term','options'->[ + 'start', + 'clear', + 'select', + 'place_player', + 'move', + 'split_point', + 'delete_point', + 'trim_path', + 'transpose', + 'play', + 'show', + 'hide', + 'prefer_smooth_play', + 'prefer_synced_play' + ]} + } +}; + +_call(command)->call(command); global_points = null; global_dimension = null; @@ -150,7 +191,6 @@ __assert_point_selected(validator) -> ); //select a custom interpolation method -interpolation(method) -> __interpolation(method, true); __interpolation(method, verbose) -> ( __prepare_path_if_needed() -> __prepare_path_if_needed_generic(); @@ -159,7 +199,7 @@ __interpolation(method, verbose) -> // or optionally __prepare_path_if_needed, if path is inefficient to compute point by point global_interpolator = if ( method == 'linear', '__interpolator_linear', - method == 'cr', '__interpolator_cr', + method == 'catmull_rom', '__interpolator_cr', method == 'gauss', _(s, p) -> __interpolator_gauB(s, p, 0), method ~ '^gauss_', ( @@ -167,8 +207,7 @@ __interpolation(method, verbose) -> type = replace(type,'_','.'); variance = round(60*number(type)); _(s, p, outer(variance)) -> __interpolator_gauB(s, p, variance); - ), - exit('Choose one of the following methods: linear, gauss, gauss_, cr') + ) ); __update(); if(verbose, 'Interpolation changed to '+method, ''); @@ -216,9 +255,6 @@ repeat(times, last_section_delay) -> stretch(percentage) -> ( if (err = __is_not_valid_for_motion(), exit(err)); - if (percentage < 25 || percentage > 400, - exit('path speed can only be speed, or slowed down 4 times. Re-call command for larger changes') - ); ratio = percentage/100; previous_path_length = global_points:(-1):1; for(global_points, _:1 = _:1*ratio ); @@ -660,4 +696,4 @@ __on_close() -> ( global_playing_path = false; global_showing_path = false; -); +); \ No newline at end of file From 96bce75db11c16c9b70c75808be01889c1f15a33 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 09:40:20 +0100 Subject: [PATCH 08/87] Update draw_beta.sc --- .../assets/carpet/scripts/draw_beta.sc | 170 ++++++++++++------ 1 file changed, 116 insertions(+), 54 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index c88739d1..954fed07 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -2,20 +2,26 @@ import('math','_euclidean_sq','_vec_length'); __config() -> { 'commands'->{ - 'sphere

'->['draw_sphere',null,true], - 'sphere
replace '->['draw_sphere',true], - 'ball
'->['draw_sphere',null,false], - 'ball
replace '->['draw_sphere',false], - 'diamond
'->['draw_diamond',null], - 'diamond
replace '->'draw_diamond', - 'pyramid
'->['draw_pyramid',null, true], - 'pyramid
replace '->['draw_pyramid', true], - 'cone
'->['draw_pyramid',null, false], - 'cone
replace '->['draw_pyramid', false], - 'cuboid
'->['draw_prism', null, true], - 'cuboid
replace '->['draw_prism', true], - 'cylinder
'->['draw_prism', null, false], - 'cylinder
replace '->['draw_prism', false] + 'sphere
'->_(c,r,b)->draw('draw_sphere',[c,r,b,null,true]), + 'sphere
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,b,rp,true]), + 'ball
'->_(c,r,b)->draw('draw_sphere',[c,r,b,null,false]), + 'ball
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,b,rp,false]), + 'diamond
hollow' -> _(c,r,b)->draw('draw_diamond',[c,r,b,null]), + 'diamond
hollow replace '->_(c,r,b,rp)->draw('draw_diamond',[c, r, b, rp]), + 'diamond
' -> _(c,r,b)->draw('draw_filled_diamond',[c,r,b,null]), + 'diamond
replace '->_(c,r,b,rp)->draw('draw_filled_diamond',[c, r, b, rp]), + 'pyramid
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,b,ho,null, true]), + 'pyramid
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,b,ho,rp,true]), + 'cone
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,b,ho,null, false]), + 'cone
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,b,ho,rp,false]), + 'cuboid
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,b,ho,null, true]), + 'cuboid
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,true]), + 'cylinder
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,b,ho,null, false]), + 'cylinder
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,false]), + if(system_info('server_dev_environment')||system_info('world_carpet_rules'):'superSecretSetting', + 'debug '->_(bool)->global_debug=bool, + ''->_()->'' + ) }, 'arguments'->{ 'center'->{'type'->'pos', 'loaded'->'true'}, @@ -25,13 +31,16 @@ __config() -> { 'orientation'->{'type'->'term', 'suggest'->['x','y','z']}, 'pointing'->{'type'->'term','suggest'->['up','down']}, 'hollow'->{'type'->'term','suggest'->['hollow','solid']}, + 'mode'->{'type'->'term','options'->['never', 'ingame', 'always']} }, 'scope'->'global' }; +global_debug=false; //"Boilerplate" code + _block_matches(existing, block_predicate) -> ( [name, block_tag, properties, nbt] = block_predicate; @@ -42,26 +51,16 @@ _block_matches(existing, block_predicate) -> (!tag || tag_matches(block_data(existing), tag)) ); -set_block(x, y, z, block, replacement)-> ( - existing = block(x, y, z); - if(block != existing && (replacement && _block_matches(existing, replacement) ), - without_updates( if (set(existing,block), - global_affected += [x, y, z]; - )); - ); -); +set_block(x, y, z, block, replacement)->//doing it like this so I can get list of blocks to place from functions + global_positions:0+=[[x, y, z], block, replacement];//Cos optional args return list always -global_affected = []; + +global_positions = [{}, 0];//using a set so I dont waste compute on duplicate positions affected(player) -> ( - affected = length(global_affected); - print(player,format('gi Filled ' + affected + ' blocks')); - - if(system_info('world_carpet_rules'):'fillUpdates', - for(global_affected,update(_));//updating if fillUpdates is true - ); - - global_affected = []; + affected = global_positions; + print(player,format('gi Filled ' + affected:1 + ' blocks')); + global_positions = [{},0]; affected ); @@ -93,55 +92,114 @@ fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)-> (!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle set_block(_x,_y,_z+offset,block, replacement) ) - ), - print(player(),format('r Error while running command: orientation can only be "x", "y" or "z", '+orientation+' is invalid.')); - global_affected = []; - ); + ) + ) ); -//drawing commands +//Drawing commands -draw_sphere(centre, radius, block, replacement, hollow)->( - scan(centre,[radius,radius,radius], - l = length_sq([_x,_y,_z]-centre); - if((l<=radius^2+radius) && (!hollow || l>=radius^2-radius), - set_block(_x, _y, _z, block, replacement) +draw(what, args)->(//custom setter cos it's easier + call(what,args);//putting blocks to be set into the first element of global_positions + + for(global_positions:0, + [pos, block, replacement]=_; + existing = block(pos); + if(block != existing && (!replacement || _block_matches(existing, replacement) ), + global_positions:1 += bool(set(existing,block)) ) ); affected(player()); ); -draw_diamond(pos, radius, block, replacement)->( +draw_sphere(args)->( + if(global_debug, start_time=unix_time()); + + [centre, radius, block, replacement, hollow] = args; + + [cx,cy,cz]=centre; + for(range(-90, 90, 45/radius), + cpitch = cos(_); + spitch = sin(_); + for(range(0, 180, 45/radius), + cyaw = cos(_)*cpitch*radius; + syaw = sin(_)*cpitch*radius; + if(hollow, + set_block(cx+cyaw,cy+spitch*radius,cz+syaw,block,replacement); + set_block(cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius,block,replacement), + for(range(-syaw,syaw+1), + set_block(cx+cyaw*cpitch,cy+spitch*radius,cz+_,block,replacement) + ) + ) + ) + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +draw_diamond(args)->( + if(global_debug, start_time=unix_time()); + + [pos, radius, block, replacement] = args; c_for(r=0, r( +draw_filled_diamond(args)->( + if(global_debug, start_time=unix_time()); + + [pos, radius, block, replacement] = args; + + for(diamond(pos,radius,radius), + set_block(_x,_y,_z,block,replacement) + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +draw_pyramid(args)->( + if(global_debug, start_time=unix_time()); + + [pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square] = args; hollow = fill_type=='hollow'; pointup = pointing=='up'; - for(range(height), - r = if(pointup, radius - radius * _ / height -1, radius * _ / height); + r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off ); - affected(player()) + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) ); -draw_prism(pos, rad, height, orientation, block, fill_type, replacement, is_square)->( +draw_prism(args)->( + if(global_debug, start_time=unix_time()); + + [pos, rad, height, orientation, block, fill_type, replacement, is_square]=args; hollow = fill_type =='hollow'; radius = rad+0.5; @@ -149,5 +207,9 @@ draw_prism(pos, rad, height, orientation, block, fill_type, replacement, is_squa for(range(height), fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off ); - affected(player()) -); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); \ No newline at end of file From 180b2285f9b0ba2bbc732da7aae9608f60fd87bb Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 09:44:43 +0100 Subject: [PATCH 09/87] moved drawing commands to separate library app now gotta import, and fix issues --- .../assets/carpet/scripts/draw_beta.sc | 132 ------------------ .../assets/carpet/scripts/shapes.scl | 132 ++++++++++++++++++ 2 files changed, 132 insertions(+), 132 deletions(-) create mode 100644 src/main/resources/assets/carpet/scripts/shapes.scl diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index 954fed07..32da53c5 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -66,36 +66,6 @@ affected(player) -> ( length_sq(vec) -> reduce(vec, _a + _*_, 0); -fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)->( - r = floor(dr); - drsq = dr^2; - if(orientation=='x', - scan(pos,0,-r,-r, - if((!hollow && (rectangle || _y^2 + _z^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_y) == r || abs(_z) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_y^2 + _z^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x+offset,_y,_z,block, replacement) - ) - ), - orientation == 'y', - scan(pos,-r,0,-r, - if((!hollow && (rectangle || _x^2 + _z^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_x) == r || abs(_z) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_x^2 + _z^2 <= drsq && (abs(_x)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x,_y+offset,_z,block, replacement) - ) - ), - orientation == 'z', - scan(pos,-r,-r,0, - if((!hollow && (rectangle || _y^2 + _x^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_y) == r || abs(_x) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x,_y,_z+offset,block, replacement) - ) - ) - ) -); - //Drawing commands draw(what, args)->(//custom setter cos it's easier @@ -111,105 +81,3 @@ draw(what, args)->(//custom setter cos it's easier affected(player()); ); - -draw_sphere(args)->( - if(global_debug, start_time=unix_time()); - - [centre, radius, block, replacement, hollow] = args; - - [cx,cy,cz]=centre; - for(range(-90, 90, 45/radius), - cpitch = cos(_); - spitch = sin(_); - for(range(0, 180, 45/radius), - cyaw = cos(_)*cpitch*radius; - syaw = sin(_)*cpitch*radius; - if(hollow, - set_block(cx+cyaw,cy+spitch*radius,cz+syaw,block,replacement); - set_block(cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius,block,replacement), - for(range(-syaw,syaw+1), - set_block(cx+cyaw*cpitch,cy+spitch*radius,cz+_,block,replacement) - ) - ) - ) - ); - - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); - return(global_positions) -); - -draw_diamond(args)->( - if(global_debug, start_time=unix_time()); - - [pos, radius, block, replacement] = args; - - c_for(r=0, r( - if(global_debug, start_time=unix_time()); - - [pos, radius, block, replacement] = args; - - for(diamond(pos,radius,radius), - set_block(_x,_y,_z,block,replacement) - ); - - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); - return(global_positions) -); - -draw_pyramid(args)->( - if(global_debug, start_time=unix_time()); - - [pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square] = args; - - hollow = fill_type=='hollow'; - pointup = pointing=='up'; - for(range(height), - r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); - fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off - ); - - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); - return(global_positions) -); - -draw_prism(args)->( - if(global_debug, start_time=unix_time()); - - [pos, rad, height, orientation, block, fill_type, replacement, is_square]=args; - - hollow = fill_type =='hollow'; - radius = rad+0.5; - - for(range(height), - fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off - ); - - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); - return(global_positions) -); \ No newline at end of file diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl new file mode 100644 index 00000000..c33ea53e --- /dev/null +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -0,0 +1,132 @@ + +draw_sphere(args)->( + if(global_debug, start_time=unix_time()); + + [centre, radius, block, replacement, hollow] = args; + + [cx,cy,cz]=centre; + for(range(-90, 90, 45/radius), + cpitch = cos(_); + spitch = sin(_); + for(range(0, 180, 45/radius), + cyaw = cos(_)*cpitch*radius; + syaw = sin(_)*cpitch*radius; + if(hollow, + set_block(cx+cyaw,cy+spitch*radius,cz+syaw,block,replacement); + set_block(cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius,block,replacement), + for(range(-syaw,syaw+1), + set_block(cx+cyaw*cpitch,cy+spitch*radius,cz+_,block,replacement) + ) + ) + ) + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +draw_diamond(args)->( + if(global_debug, start_time=unix_time()); + + [pos, radius, block, replacement] = args; + + c_for(r=0, r( + if(global_debug, start_time=unix_time()); + + [pos, radius, block, replacement] = args; + + for(diamond(pos,radius,radius), + set_block(_x,_y,_z,block,replacement) + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +draw_pyramid(args)->( + if(global_debug, start_time=unix_time()); + + [pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square] = args; + + hollow = fill_type=='hollow'; + pointup = pointing=='up'; + for(range(height), + r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); + fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +draw_prism(args)->( + if(global_debug, start_time=unix_time()); + + [pos, rad, height, orientation, block, fill_type, replacement, is_square]=args; + + hollow = fill_type =='hollow'; + radius = rad+0.5; + + for(range(height), + fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off + ); + + if(global_debug, + print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) + ); + return(global_positions) +); + +fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)->( + r = floor(dr); + drsq = dr^2; + if(orientation=='x', + scan(pos,0,-r,-r, + if((!hollow && (rectangle || _y^2 + _z^2 <= drsq))||//if not hollow, vry simple + (hollow && ((rectangle && (abs(_y) == r || abs(_z) ==r)) || //If hollow and it's a rectangle + (!rectangle && (_y^2 + _z^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle + set_block(_x+offset,_y,_z,block, replacement) + ) + ), + orientation == 'y', + scan(pos,-r,0,-r, + if((!hollow && (rectangle || _x^2 + _z^2 <= drsq))||//if not hollow, vry simple + (hollow && ((rectangle && (abs(_x) == r || abs(_z) ==r)) || //If hollow and it's a rectangle + (!rectangle && (_x^2 + _z^2 <= drsq && (abs(_x)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle + set_block(_x,_y+offset,_z,block, replacement) + ) + ), + orientation == 'z', + scan(pos,-r,-r,0, + if((!hollow && (rectangle || _y^2 + _x^2 <= drsq))||//if not hollow, vry simple + (hollow && ((rectangle && (abs(_y) == r || abs(_x) ==r)) || //If hollow and it's a rectangle + (!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle + set_block(_x,_y,_z+offset,block, replacement) + ) + ) + ) +); From 8eba0b9b8afdab2eff485f5eacefe85ee9cf5b1f Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 09:46:28 +0100 Subject: [PATCH 10/87] removed debug and easter egg --- .../assets/carpet/scripts/draw_beta.sc | 6 ------ .../assets/carpet/scripts/shapes.scl | 20 ------------------- 2 files changed, 26 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index 32da53c5..c29aa359 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -18,10 +18,6 @@ __config() -> { 'cuboid
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,true]), 'cylinder
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,b,ho,null, false]), 'cylinder
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,false]), - if(system_info('server_dev_environment')||system_info('world_carpet_rules'):'superSecretSetting', - 'debug '->_(bool)->global_debug=bool, - ''->_()->'' - ) }, 'arguments'->{ 'center'->{'type'->'pos', 'loaded'->'true'}, @@ -36,7 +32,6 @@ __config() -> { 'scope'->'global' }; -global_debug=false; //"Boilerplate" code @@ -64,7 +59,6 @@ affected(player) -> ( affected ); -length_sq(vec) -> reduce(vec, _a + _*_, 0); //Drawing commands diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index c33ea53e..170f2e43 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -1,6 +1,5 @@ draw_sphere(args)->( - if(global_debug, start_time=unix_time()); [centre, radius, block, replacement, hollow] = args; @@ -21,14 +20,10 @@ draw_sphere(args)->( ) ); - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); return(global_positions) ); draw_diamond(args)->( - if(global_debug, start_time=unix_time()); [pos, radius, block, replacement] = args; @@ -44,14 +39,10 @@ draw_diamond(args)->( ) ); - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); return(global_positions) ); draw_filled_diamond(args)->( - if(global_debug, start_time=unix_time()); [pos, radius, block, replacement] = args; @@ -59,14 +50,10 @@ draw_filled_diamond(args)->( set_block(_x,_y,_z,block,replacement) ); - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); return(global_positions) ); draw_pyramid(args)->( - if(global_debug, start_time=unix_time()); [pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square] = args; @@ -77,14 +64,10 @@ draw_pyramid(args)->( fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off ); - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); return(global_positions) ); draw_prism(args)->( - if(global_debug, start_time=unix_time()); [pos, rad, height, orientation, block, fill_type, replacement, is_square]=args; @@ -95,9 +78,6 @@ draw_prism(args)->( fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off ); - if(global_debug, - print(player(),format('gi Time taken: '+(unix_time()-start_time)+'ms')) - ); return(global_positions) ); From ac031090ef04e3c7c85f21e3cced14a2938773c4 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 10:14:28 +0100 Subject: [PATCH 11/87] making the shapes in shapes.scl purely mathematical, no strings attached, only returning a set of the positions to iterate over. --- .../assets/carpet/scripts/draw_beta.sc | 68 ++++++---------- .../assets/carpet/scripts/shapes.scl | 81 +++++++++---------- 2 files changed, 63 insertions(+), 86 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index c29aa359..53ce82d5 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -1,23 +1,23 @@ -import('math','_euclidean_sq','_vec_length'); +import('shapes','draw_sphere', 'draw_diamond', 'draw_filled_diamond', 'draw_pyramid', 'draw_prism'); //importing all the shape funcs from the other app __config() -> { 'commands'->{ - 'sphere
'->_(c,r,b)->draw('draw_sphere',[c,r,b,null,true]), - 'sphere
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,b,rp,true]), - 'ball
'->_(c,r,b)->draw('draw_sphere',[c,r,b,null,false]), - 'ball
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,b,rp,false]), - 'diamond
hollow' -> _(c,r,b)->draw('draw_diamond',[c,r,b,null]), - 'diamond
hollow replace '->_(c,r,b,rp)->draw('draw_diamond',[c, r, b, rp]), - 'diamond
' -> _(c,r,b)->draw('draw_filled_diamond',[c,r,b,null]), - 'diamond
replace '->_(c,r,b,rp)->draw('draw_filled_diamond',[c, r, b, rp]), - 'pyramid
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,b,ho,null, true]), - 'pyramid
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,b,ho,rp,true]), - 'cone
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,b,ho,null, false]), - 'cone
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,b,ho,rp,false]), - 'cuboid
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,b,ho,null, true]), - 'cuboid
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,true]), - 'cylinder
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,b,ho,null, false]), - 'cylinder
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,b,ho,rp,false]), + 'sphere
'->_(c,r,b)->draw('draw_sphere',[c,r, true],b,null), + 'sphere
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,true],b,rp), + 'ball
'->_(c,r,b)->draw('draw_sphere',[c,r,false],b,null), + 'ball
replace '->_(c,r,b,rp)->draw('draw_sphere',[c,r,false],b,rp), + 'diamond
hollow' -> _(c,r,b)->draw('draw_diamond',[c,r],b,null), + 'diamond
hollow replace '->_(c,r,b,rp)->draw('draw_diamond',[c, r], b, rp), + 'diamond
' -> _(c,r,b)->draw('draw_filled_diamond',[c,r],b,null), + 'diamond
replace '->_(c,r,b,rp)->draw('draw_filled_diamond',[c, r], b, rp), + 'pyramid
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,ho, true],b,null), + 'pyramid
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,ho,true],b,rp), + 'cone
'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,ho, false],b,null), + 'cone
replace '->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,ho,false],b,rp), + 'cuboid
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,ho, true],b,null), + 'cuboid
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,ho,true],b,rp), + 'cylinder
'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,ho, false],b,null), + 'cylinder
replace '->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,ho,false],b,rp), }, 'arguments'->{ 'center'->{'type'->'pos', 'loaded'->'true'}, @@ -32,10 +32,6 @@ __config() -> { 'scope'->'global' }; - -//"Boilerplate" code - - _block_matches(existing, block_predicate) -> ( [name, block_tag, properties, nbt] = block_predicate; @@ -46,32 +42,16 @@ _block_matches(existing, block_predicate) -> (!tag || tag_matches(block_data(existing), tag)) ); -set_block(x, y, z, block, replacement)->//doing it like this so I can get list of blocks to place from functions - global_positions:0+=[[x, y, z], block, replacement];//Cos optional args return list always +draw(what, args, block, replacement)->(//custom setter cos it's easier + positions = call(what,args); //returning blocks to be set + affected = 0 -global_positions = [{}, 0];//using a set so I dont waste compute on duplicate positions - -affected(player) -> ( - affected = global_positions; - print(player,format('gi Filled ' + affected:1 + ' blocks')); - global_positions = [{},0]; - affected -); - - -//Drawing commands - -draw(what, args)->(//custom setter cos it's easier - call(what,args);//putting blocks to be set into the first element of global_positions - - for(global_positions:0, - [pos, block, replacement]=_; - existing = block(pos); + for(positions, + existing = block(_); if(block != existing && (!replacement || _block_matches(existing, replacement) ), - global_positions:1 += bool(set(existing,block)) + affected += bool(set(existing,block)) ) ); - - affected(player()); + print(player,format('gi Filled ' + affected + ' blocks')); ); diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index 170f2e43..7659c9c6 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -1,8 +1,14 @@ +//a library app, mostly for the draw command, which allows to import these shape-drawing functions into your own app. +//Arguments for each are to be given as a singleton list, for ease of use. This is with the exception of fill_flat, with +//the assumption that it is unlikely to be used outside of internal calls, but it is still available to be called if necessary. +//Make sure you read the arguments and input them in the correct order, or it will mess up. +//these functions return a set of all the positions in the shape, given the parameters, without repeating a position. +//This way, you can efficiently iterate over the positions once having generated them. See draw_beta.sc for an example. + draw_sphere(args)->( - - [centre, radius, block, replacement, hollow] = args; - + [centre, radius, hollow] = args; + positions = {}; [cx,cy,cz]=centre; for(range(-90, 90, 45/radius), cpitch = cos(_); @@ -11,85 +17,75 @@ draw_sphere(args)->( cyaw = cos(_)*cpitch*radius; syaw = sin(_)*cpitch*radius; if(hollow, - set_block(cx+cyaw,cy+spitch*radius,cz+syaw,block,replacement); - set_block(cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius,block,replacement), + positions += [cx+cyaw,cy+spitch*radius,cz+syaw]; + positions += [cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius], for(range(-syaw,syaw+1), - set_block(cx+cyaw*cpitch,cy+spitch*radius,cz+_,block,replacement) + positions += [cx+cyaw*cpitch,cy+spitch*radius,cz+_] ) ) ) ); - - return(global_positions) + return(positions) ); draw_diamond(args)->( - - [pos, radius, block, replacement] = args; - + [pos, radius] = args; + positions = {}; c_for(r=0, r( - - [pos, radius, block, replacement] = args; - + [pos, radius] = args; + positions = {}; for(diamond(pos,radius,radius), - set_block(_x,_y,_z,block,replacement) + positions += pos(_) ); - - return(global_positions) + return(positions) ); draw_pyramid(args)->( - - [pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square] = args; - + [pos, radius, height, pointing, orientation, fill_type, is_square] = args;//todo change to hollow boolean arg + positions = {}; hollow = fill_type=='hollow'; pointup = pointing=='up'; for(range(height), r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); - fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off + positions += fill_flat(pos, _, r, is_square, orientation, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow))//Always close bottom off ); - - return(global_positions) + return(positions) ); draw_prism(args)->( - - [pos, rad, height, orientation, block, fill_type, replacement, is_square]=args; - + [pos, rad, height, orientation, fill_type, is_square]=args;//todo change to hollow boolean arg + positions = {}; hollow = fill_type =='hollow'; radius = rad+0.5; - for(range(height), - fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off + positions += fill_flat(pos, _, radius, is_square, orientation, if(_==0 || _==height-1,false,hollow))//Always close ends off ); - - return(global_positions) + return(positions) ); -fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)->( +fill_flat(pos, offset, dr, rectangle, orientation, hollow)->( r = floor(dr); drsq = dr^2; + positions = {}; if(orientation=='x', scan(pos,0,-r,-r, if((!hollow && (rectangle || _y^2 + _z^2 <= drsq))||//if not hollow, vry simple (hollow && ((rectangle && (abs(_y) == r || abs(_z) ==r)) || //If hollow and it's a rectangle (!rectangle && (_y^2 + _z^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x+offset,_y,_z,block, replacement) + positions += [_x+offset,_y,_z] ) ), orientation == 'y', @@ -97,7 +93,7 @@ fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)-> if((!hollow && (rectangle || _x^2 + _z^2 <= drsq))||//if not hollow, vry simple (hollow && ((rectangle && (abs(_x) == r || abs(_z) ==r)) || //If hollow and it's a rectangle (!rectangle && (_x^2 + _z^2 <= drsq && (abs(_x)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x,_y+offset,_z,block, replacement) + positions += [_x,_y+offset,_z] ) ), orientation == 'z', @@ -105,8 +101,9 @@ fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)-> if((!hollow && (rectangle || _y^2 + _x^2 <= drsq))||//if not hollow, vry simple (hollow && ((rectangle && (abs(_y) == r || abs(_x) ==r)) || //If hollow and it's a rectangle (!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle - set_block(_x,_y,_z+offset,block, replacement) + positions += [_x,_y,_z+offset] ) ) - ) + ); + return(positions) ); From eafc618792d64e04d205aafa1fb9201754d89a77 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 10:14:57 +0100 Subject: [PATCH 12/87] oboi, remember that caching bs? I apparently forgot to remove this bit lol --- src/main/resources/assets/carpet/scripts/draw_beta.sc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index 53ce82d5..d1c9992e 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -27,7 +27,6 @@ __config() -> { 'orientation'->{'type'->'term', 'suggest'->['x','y','z']}, 'pointing'->{'type'->'term','suggest'->['up','down']}, 'hollow'->{'type'->'term','suggest'->['hollow','solid']}, - 'mode'->{'type'->'term','options'->['never', 'ingame', 'always']} }, 'scope'->'global' }; From fa75401278da6c4a7634e237c91bba8621f0ab22 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 10:17:06 +0100 Subject: [PATCH 13/87] minor editing tweaks --- src/main/resources/assets/carpet/scripts/draw_beta.sc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc index d1c9992e..0b8ba312 100644 --- a/src/main/resources/assets/carpet/scripts/draw_beta.sc +++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc @@ -28,7 +28,7 @@ __config() -> { 'pointing'->{'type'->'term','suggest'->['up','down']}, 'hollow'->{'type'->'term','suggest'->['hollow','solid']}, }, - 'scope'->'global' + 'scope'->'global'//todo decide whether it rly needs to be global or not... }; _block_matches(existing, block_predicate) -> @@ -48,7 +48,7 @@ draw(what, args, block, replacement)->(//custom setter cos it's easier for(positions, existing = block(_); - if(block != existing && (!replacement || _block_matches(existing, replacement) ), + if(block != existing && (!replacement || _block_matches(existing, replacement)), affected += bool(set(existing,block)) ) ); From 467767a8e7c52f3e17f97ecd381129936b534aea Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 12:06:17 +0100 Subject: [PATCH 14/87] using bresenham algorithm for circles to make it more efficient --- .../assets/carpet/scripts/shapes.scl | 151 +++++++++++++++--- 1 file changed, 127 insertions(+), 24 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index 7659c9c6..b8b085c2 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -1,9 +1,9 @@ -//a library app, mostly for the draw command, which allows to import these shape-drawing functions into your own app. -//Arguments for each are to be given as a singleton list, for ease of use. This is with the exception of fill_flat, with -//the assumption that it is unlikely to be used outside of internal calls, but it is still available to be called if necessary. +//This is a library app, mostly for the draw command, which allows to import these shape-drawing functions into your own app. +//Arguments for each are to be given as a singleton list, for ease of use. This is with the exception of the fill_flat functions, with +//the assumption that they're unlikely to be used outside of internal calls, but it is still available to be called if necessary. //Make sure you read the arguments and input them in the correct order, or it will mess up. //these functions return a set of all the positions in the shape, given the parameters, without repeating a position. -//This way, you can efficiently iterate over the positions once having generated them. See draw_beta.sc for an example. +//This way, you can efficiently iterate over the positions once having generated them. See draw_beta.sc for an example. todo change to draw once renamed draw_sphere(args)->( @@ -76,34 +76,137 @@ draw_prism(args)->( return(positions) ); -fill_flat(pos, offset, dr, rectangle, orientation, hollow)->( +fill_flat(pos, offset, dr, rectangle, orientation, hollow)-> + if(rectangle, + fill_flat_rectangle(pos, offset, dr, orientation, hollow), + fill_flat_circle(pos, offset, dr, orientation, hollow) + ); + +fill_flat_circle(pos, offset, dr, orientation, hollow) ->( //Bresenham circle algorithm to be super efficient r = floor(dr); - drsq = dr^2; + a=0; + b=r; + d=3-(2*r); + positions = {}; - if(orientation=='x', - scan(pos,0,-r,-r, - if((!hollow && (rectangle || _y^2 + _z^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_y) == r || abs(_z) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_y^2 + _z^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - positions += [_x+offset,_y,_z] + + positions += fill_flat_circle_points(pos, orientation, offset, hollow, a, b); + + while(a<=b, 10^10, + if(d<=0, + d += (4*a)+6, + d += (4*a)-(4*b)+10; + b=b-1 + ); + a+=1; + positions += fill_flat_circle_points(pos, orientation, offset, hollow, a, b) + ); + return(positions) +); + +fill_flat_circle_points(pos, orientation, offset, hollow, a, b) ->( + [x, y, z] = pos; + positions = {}; + if(orientation == 'x', + if(hollow, + positions += [x + offset, a+y, b+z]; + positions += [x + offset, a+y,-b+z]; + positions += [x + offset,-a+y,-b+z]; + positions += [x + offset,-a+y, b+z]; + positions += [x + offset, b+y, a+z]; + positions += [x + offset, b+y,-a+z]; + positions += [x + offset,-b+y,-a+z]; + positions += [x + offset,-b+y, a+z], + c_for(c = -a, c<=a, c+=1, + positions += [x + offset,c+y, b + z]; + positions += [x + offset,c+y,-b + z] + ); + c_for(c = -b, c<=b, c+=1, + positions += [x + offset, a+y,c + z]; + positions += [x + offset,-a+y,c + z] ) ), - orientation == 'y', - scan(pos,-r,0,-r, - if((!hollow && (rectangle || _x^2 + _z^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_x) == r || abs(_z) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_x^2 + _z^2 <= drsq && (abs(_x)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle - positions += [_x,_y+offset,_z] + orientation=='y', + if(hollow, + positions += [ a+x, y + offset, b+z]; + positions += [ a+x, y + offset,-b+z]; + positions += [-a+x, y + offset,-b+z]; + positions += [-a+x, y + offset, b+z]; + positions += [ b+x, y + offset, a+z]; + positions += [ b+x, y + offset,-a+z]; + positions += [-b+x, y + offset,-a+z]; + positions += [-b+x, y + offset, a+z], + c_for(c = -a, c<=a, c+=1, + positions += [c+x,y + offset, b + z]; + positions += [c+x,y + offset,-b + z] + ); + c_for(c = -b, c<=b, c+=1, + positions += [ a+x,y + offset,c + z]; + positions += [-a+x,y + offset,c + z] ) ), - orientation == 'z', - scan(pos,-r,-r,0, - if((!hollow && (rectangle || _y^2 + _x^2 <= drsq))||//if not hollow, vry simple - (hollow && ((rectangle && (abs(_y) == r || abs(_x) ==r)) || //If hollow and it's a rectangle - (!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle - positions += [_x,_y,_z+offset] + orientation=='z', + if(hollow, + positions += [ a+x, b+y, z + offset]; + positions += [ a+x,-b+y, z + offset]; + positions += [-a+x,-b+y, z + offset]; + positions += [-a+x, b+y, z + offset]; + positions += [ b+x, a+y, z + offset]; + positions += [ b+x,-a+y, z + offset]; + positions += [-b+x,-a+y, z + offset]; + positions += [-b+x, a+y, z + offset], + c_for(c = -a, c<=a, c+=1, + positions += [c+x, b + y,z + offset]; + positions += [c+x,-b + y,z + offset] + ); + c_for(c = -b, c<=b, c+=1, + positions += [ a+x,c + y,z + offset]; + positions += [-a+x,c + y,z + offset] ) ) + ) +); + +fill_flat_rectangle(pos, offset, dr, orientation, hollow) -> ( + r = floor(dr); + drsq = dr^2; + positions = {}; + if(orientation == 'x', + c_for(a = -r, a <=r, a +=1, + if(hollow, + positions += [r + offset, a, r]; + positions += [r + offset, a,-r]; + positions += [r + offset, r, a]; + positions += [r + offset,-r, a], + c_for(b = -r, b <=r, b +=1, + positions += [r + offset, a, b] + ) + ) + ), + orientation == 'y', + c_for(a = -r, a <=r, a +=1, + if(hollow, + positions += [ r, r + offset,a]; + positions += [-r, r + offset,a]; + positions += [a, r + offset, r]; + positions += [a, r + offset,-r], + c_for(b = -r, b <=r, b +=1, + positions += [a, r + offset, b] + ) + ) + ), + orientation == 'z', + c_for(a = -r, a <=r, a +=1, + if(hollow, + positions += [ r, a,r + offset]; + positions += [-r, a,r + offset]; + positions += [a, r, r + offset]; + positions += [a,-r, r + offset], + c_for(b = -r, b <=r, b +=1, + positions += [b, a, r + offset] + ) + ) + ), ); return(positions) ); From facac439cfe5d24f57d4a0a36ace90fdcaabd802 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Wed, 24 Feb 2021 12:11:51 +0100 Subject: [PATCH 15/87] compacting hollow code --- src/main/resources/assets/carpet/scripts/shapes.scl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index b8b085c2..440b0425 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -60,7 +60,7 @@ draw_pyramid(args)->( pointup = pointing=='up'; for(range(height), r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); - positions += fill_flat(pos, _, r, is_square, orientation, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow))//Always close bottom off + positions += fill_flat(pos, _, r, is_square, orientation, !((pointup&&_==0)||(!pointup && _==height-1)) && hollow)//Always close bottom off ); return(positions) ); @@ -71,7 +71,7 @@ draw_prism(args)->( hollow = fill_type =='hollow'; radius = rad+0.5; for(range(height), - positions += fill_flat(pos, _, radius, is_square, orientation, if(_==0 || _==height-1,false,hollow))//Always close ends off + positions += fill_flat(pos, _, radius, is_square, orientation, !(_==0 || _==height-1) && hollow)//Always close ends off ); return(positions) ); From f202c024cf60a31325d1d2e09d12d43160d28aee Mon Sep 17 00:00:00 2001 From: replaceitem Date: Wed, 24 Feb 2021 17:55:45 +0100 Subject: [PATCH 16/87] made entity~'display_name' return a formatted text --- src/main/java/carpet/script/value/EntityValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 0a1c3506..0e08f7a0 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -416,7 +416,7 @@ public class EntityValue extends Value put("motion_z", (e, a) -> new NumericValue(e.getVelocity().z)); put("on_ground", (e, a) -> new NumericValue(e.isOnGround())); put("name", (e, a) -> new StringValue(e.getName().getString())); - put("display_name", (e, a) -> new StringValue(e.getDisplayName().getString())); + put("display_name", (e, a) -> new FormattedTextValue(e.getDisplayName())); put("command_name", (e, a) -> new StringValue(e.getEntityName())); put("custom_name", (e, a) -> e.hasCustomName()?new StringValue(e.getCustomName().getString()):Value.NULL); put("type", (e, a) -> new StringValue(nameFromRegistryId(Registry.ENTITY_TYPE.getId(e.getType())))); From 523f5e7438b35d7cb590290c8147ed081556570e Mon Sep 17 00:00:00 2001 From: replaceitem Date: Wed, 24 Feb 2021 18:16:17 +0100 Subject: [PATCH 17/87] Added to docs --- docs/scarpet/api/Entities.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index bbd2872e..5e013034 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -177,7 +177,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t ### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')` -String of entity name +String of entity name or formatted text in the case of `display_name`
 query(e,'name')  => Leatherworker

From 841b3f54912a04c714258a0e6c364496cc33afda Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Wed, 24 Feb 2021 18:27:31 -0400
Subject: [PATCH 18/87] 1.4.28

---
 gradle.properties                        | 2 +-
 src/main/java/carpet/CarpetSettings.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index a9261de6..d74a5b2d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G
 	fabric_version=0.29.4+1.16
 
 # Mod Properties
-	mod_version = 1.4.27
+	mod_version = 1.4.28
 	maven_group = carpet
 	archives_base_name = fabric-carpet
 
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index 83c4c3ec..38f9ffba 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT;
 @SuppressWarnings("CanBeFinal")
 public class CarpetSettings
 {
-    public static final String carpetVersion = "1.4.27+v210217";
+    public static final String carpetVersion = "1.4.28+v210224";
     public static final Logger LOG = LogManager.getLogger("carpet");
     public static boolean skipGenerationChecks = false;
     public static boolean impendingFillSkipUpdates = false;

From ae63c7be1d806e559fa9fadbde759ebd92559db3 Mon Sep 17 00:00:00 2001
From: Ghoulboy78 
Date: Thu, 25 Feb 2021 09:32:00 +0100
Subject: [PATCH 19/87] registering as built-in library (thx @altrisi)

---
 src/main/java/carpet/script/CarpetScriptServer.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 8634ca00..e86eb4c9 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -93,6 +93,7 @@ public class CarpetScriptServer
         registerBuiltInScript(BundledModule.carpetNative("chunk_display", false));
         registerBuiltInScript(BundledModule.carpetNative("ai_tracker", false));
         registerBuiltInScript(BundledModule.carpetNative("draw_beta", false));
+        registerBuiltInScript(BundledModule.carpetNative("shapes", true));
         registerBuiltInScript(BundledModule.carpetNative("distance_beta", false));
     }
 

From 84797bef0e0e86167d60db81ee45f0b6a4b1842f Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 25 Feb 2021 21:59:36 -0400
Subject: [PATCH 20/87] restrict player placement to buildable area.

---
 src/main/java/carpet/commands/PlayerCommand.java | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/main/java/carpet/commands/PlayerCommand.java b/src/main/java/carpet/commands/PlayerCommand.java
index 9aa9297b..ee52afe4 100644
--- a/src/main/java/carpet/commands/PlayerCommand.java
+++ b/src/main/java/carpet/commands/PlayerCommand.java
@@ -23,6 +23,7 @@ import net.minecraft.server.command.ServerCommandSource;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.util.math.Vec2f;
 import net.minecraft.util.math.Vec3d;
@@ -271,7 +272,13 @@ public class PlayerCommand
             Messenger.m(context.getSource(), "rb Player name: "+playerName+" is too long");
             return 0;
         }
+
         MinecraftServer server = source.getMinecraftServer();
+        if (!World.isInBuildLimit(new BlockPos(pos.x, pos.y, pos.z)))
+        {
+            Messenger.m(context.getSource(), "rb Player "+playerName+" cannot be placed outside of the world");
+            return 0;
+        }
         PlayerEntity player = EntityPlayerMPFake.createFake(playerName, server, pos.x, pos.y, pos.z, facing.y, facing.x, dimType, mode);
         if (player == null)
         {

From ca4db433c1e614ddb11bdd3a319b8af00f3dfc1e Mon Sep 17 00:00:00 2001
From: replaceitem <40722305+replaceitem@users.noreply.github.com>
Date: Tue, 2 Mar 2021 03:10:49 +0100
Subject: [PATCH 21/87] Fixed slice() returning empty string when referring to
 last character  (#751)

* Fixed slice() returning empty string when referring to last character
---
 docs/scarpet/language/Containers.md              | 4 ++--
 src/main/java/carpet/script/value/ListValue.java | 2 +-
 src/main/java/carpet/script/value/Value.java     | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md
index 07d6993a..882f4f7f 100644
--- a/docs/scarpet/language/Containers.md
+++ b/docs/scarpet/language/Containers.md
@@ -187,10 +187,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t
 cannot go back nor track where they are in the sequence (see examples).
 
 
-slice([0,1,2,3,4,5], 1, 3)  => [1, 2, 3]
+slice([0,1,2,3,4,5], 1, 3)  => [1, 2]
 slice('foobar', 0, 1)  => 'f'
 slice('foobar', 3)  => 'bar'
-slice(range(10), 3, 5)  => [3, 4, 5]
+slice(range(10), 3, 5)  => [3, 4]
 slice(range(10), 5)  => [5, 6, 7, 8, 9]
 r = range(100); [slice(r, 5, 7), slice(r, 1, 3)]  => [[5, 6], [8, 9]]
 
diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index 7fd693eb..de316668 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -341,7 +341,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf int from = normalizeIndex(fromDesc, size); if (toDesc == null) return new ListValue((Collection) getItems().subList(from, size)); - int to = normalizeIndex(toDesc, size); + int to = normalizeIndex(toDesc, size+1); if (from > to) return ListValue.of(); return new ListValue((Collection) getItems().subList(from, to)); diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 5f228c7d..5548d83f 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -186,7 +186,7 @@ public abstract class Value implements Comparable, Cloneable int size = value.length(); int from = ListValue.normalizeIndex(fromDesc, size); if (toDesc == null) return new StringValue(value.substring(from)); - int to = ListValue.normalizeIndex(toDesc, size); + int to = ListValue.normalizeIndex(toDesc, size+1); if (from > to) return StringValue.EMPTY; return new StringValue(value.substring(from, to)); } From 69a0eeed7dc6d19ccfdb8d393af7bf3be908b99a Mon Sep 17 00:00:00 2001 From: Andrew S Date: Mon, 1 Mar 2021 21:48:31 -0500 Subject: [PATCH 22/87] Add `pingPlayerListLimit` setting (#704) * Update CarpetSettings.java * Add pingPlayerListLimit * Fix formatting & fail softer --- src/main/java/carpet/CarpetSettings.java | 8 ++++++++ ...MinecraftServer_pingPlayerSampleLimit.java | 19 +++++++++++++++++++ src/main/resources/carpet.mixins.json | 1 + 3 files changed, 28 insertions(+) create mode 100644 src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index a41a478d..6a1ab764 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -532,6 +532,14 @@ public class CarpetSettings ) public static int maxEntityCollisions = 0; + @Rule( + desc = "Customizable server list ping (Multiplayer menu) playerlist sample limit", + options = {"0", "12", "20", "40"}, + category = CREATIVE, + strict = false, + validate = Validator.NONNEGATIVE_NUMBER.class + ) + public static int pingPlayerListLimit = 12; /* @Rule( diff --git a/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java b/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java new file mode 100644 index 00000000..79e2e27d --- /dev/null +++ b/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java @@ -0,0 +1,19 @@ +package carpet.mixins; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import carpet.CarpetSettings; +import net.minecraft.server.MinecraftServer; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServer_pingPlayerSampleLimit +{ + + @ModifyConstant(method = "tick", constant = @Constant(intValue = 12), require = 0, allow = 1) + private int modifyPlayerSampleLimit(int value) + { + return CarpetSettings.pingPlayerListLimit; + } +} diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index bcbf958c..e6174d9d 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -24,6 +24,7 @@ "ServerPlayNetworkHandler_interactionUpdatesMixin", "ServerMetadata_motdMixin", + "MinecraftServer_pingPlayerSampleLimit", "MinecraftServer_coreMixin", From ec25bf4032c42d6f452487982e8d382f75bdc673 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 2 Mar 2021 00:04:05 -0400 Subject: [PATCH 23/87] Should fix NPE when blowing stuff up, fixes #728 --- src/main/java/carpet/helpers/OptimizedExplosion.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java index 3130f30a..1c124ce5 100644 --- a/src/main/java/carpet/helpers/OptimizedExplosion.java +++ b/src/main/java/carpet/helpers/OptimizedExplosion.java @@ -107,20 +107,22 @@ public class OptimizedExplosion explosionSound++; + Entity explodingEntity = eAccess.getEntity(); for (int k2 = 0; k2 < entitylist.size(); ++k2) { Entity entity = entitylist.get(k2); - if (entity == eAccess.getEntity()) { + + if (entity == explodingEntity) { // entitylist.remove(k2); removeFast(entitylist, k2); k2--; continue; } - if (entity instanceof TntEntity && - entity.getX() == eAccess.getEntity().getX() && - entity.getY() == eAccess.getEntity().getY() && - entity.getZ() == eAccess.getEntity().getZ()) { + if (entity instanceof TntEntity && explodingEntity != null && + entity.getX() == explodingEntity.getX() && + entity.getY() == explodingEntity.getY() && + entity.getZ() == explodingEntity.getZ()) { if (eLogger != null) { eLogger.onEntityImpacted(entity, new Vec3d(0,-0.9923437498509884d, 0)); } From 8f592735cad444692a8057be29a7bef8bbc0fa21 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 2 Mar 2021 09:16:56 -0400 Subject: [PATCH 24/87] fixes --- src/main/resources/assets/carpet/scripts/camera.sc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 8f21ced5..4ed58ef6 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -63,7 +63,7 @@ __config() ->{ ''->'_call', 'add '->'add', 'prepend '->'prepend', - 'duration ', + 'duration ' -> 'duration', 'save_as '->'save_as', 'load '->'load', 'interpolation '->['__interpolation',true], @@ -76,8 +76,8 @@ __config() ->{ 'seconds'->{'type'->'int','suggest'->[]}, 'last_delay'->{'type'->'int','suggest'->[]}, 'name'->{'type'->'string','suggest'->[]}, - 'interpolation'->{'type'->'term','options'->['linear','catmull_rom']} - 'factor'->{'type'->'int','min'->25,'max'->'400'}, + 'interpolation'->{'type'->'term','options'->['linear','catmull_rom']}, + 'factor'->{'type'->'int','min'->25,'max'->400}, 'command'->{'type'->'term','options'->[ 'start', 'clear', @@ -212,7 +212,7 @@ __interpolation(method, verbose) -> __update(); if(verbose, 'Interpolation changed to '+method, ''); ); -__interpolation('cr', false); +__interpolation('catmull_rom', false); // adds a point to the end of the path with delay in seconds add(delay) -> From 44101ddd3d60f640a4608a9bcb66dd60dc9c2035 Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 2 Mar 2021 14:26:05 +0100 Subject: [PATCH 25/87] Undeprecate and filter to user-defined --- .../carpet/script/language/ControlFlow.java | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 029bf8ac..b704a908 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -7,6 +7,7 @@ import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ThrowStatement; +import carpet.script.exception.Throwables; import carpet.script.value.StringValue; import carpet.script.value.Value; @@ -49,7 +50,6 @@ public class ControlFlow { switch (lv.size()) { case 0: - c.host.issueDeprecation("throw() (without id)"); throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); case 1: throw new ThrowStatement(lv.get(0).evalValue(c)); @@ -65,7 +65,7 @@ public class ControlFlow { expression.addLazyFunction("try", -1, (c, t, lv) -> { if (lv.size()==0) - throw new InternalExpressionException("'try' needs at least an expression block, a filter, and a catch_epr"); + throw new InternalExpressionException("'try' needs at least an expression block, and either a catch_epr, or a number of pairs of filters and catch_expr"); try { Value retval = lv.get(0).evalValue(c, t); @@ -77,21 +77,36 @@ public class ControlFlow { throw ret; if (lv.size() == 1) { - c.host.issueDeprecation("try(expr) (without filter and catch expression)"); + if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + throw ret; return (c_, t_) -> Value.NULL; } - // This is outside since it is used in try(expr,catch_expr) + if (lv.size() == 0 || lv.size() % 2 == 0) + { + throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch_expr or a number of pairs of filters and catch_expr"); + } + + Value val = null; // This is always assigned at some point, just the compiler doesn't know + LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); LazyValue __msg = c.getVariable("_msg"); c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - Value val = null; // This is always assigned at some point, just the compiler doesn't know - if (lv.size() >= 3) + + boolean handled; + if (lv.size() == 2) + { + if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + handled = false; + else { + val = lv.get(1).evalValue(c, t); + handled = true; + } + } + else { - if (lv.size() % 2 == 0) - throw new InternalExpressionException("Multi-catch block needs a catch expression for every filter"); int pointer = 1; - boolean handled = false; + handled = false; while (pointer < lv.size() -1) { if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) @@ -102,20 +117,14 @@ public class ControlFlow { } pointer += 2; } - if (!handled) - { - c.setVariable("_", __); - c.setVariable("_msg", __msg); - throw ret; - } } - else - { - c.host.issueDeprecation("try(expr, catch_expr) (without filter)"); - val = lv.get(1).evalValue(c, t); - } - c.setVariable("_",__); + c.setVariable("_", __); c.setVariable("_msg", __msg); + + if (!handled) + { + throw ret; + } Value retval = val; return (c_, t_) -> retval; } From 1ae358909bd9c908fe5e0b7fe9ee96bb65534ba0 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 2 Mar 2021 23:33:51 -0400 Subject: [PATCH 26/87] `true` and `false` represent now proper boolean values, not just 0 and 1. All other previous arithmetic holds. --- docs/scarpet/language/Containers.md | 26 ++++----- .../language/LoopsAndHigherOrderFunctions.md | 8 +-- docs/scarpet/language/Operators.md | 47 +++++++-------- docs/scarpet/language/SystemFunctions.md | 49 ++++++++-------- .../scarpet/language/VariablesAndConstants.md | 4 +- src/main/java/carpet/script/language/Sys.java | 6 +- .../carpet/script/value/BooleanValue.java | 58 +++++++++++++++++++ .../java/carpet/script/value/NullValue.java | 3 +- src/main/java/carpet/script/value/Value.java | 10 ++-- 9 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 src/main/java/carpet/script/value/BooleanValue.java diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md index 882f4f7f..66c8ff17 100644 --- a/docs/scarpet/language/Containers.md +++ b/docs/scarpet/language/Containers.md @@ -23,12 +23,12 @@ In case to simplify the access with nested objects, you can add chain of address than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
-get(l(range(10)), 5)  => 5
-get(l(range(10)), -1)  => 9
-get(l(range(10)), 10)  => 0
-l(range(10)):93  => 3
+get([range(10)], 5)  => 5
+get([range(10)], -1)  => 9
+get([range(10)], 10)  => 0
+[range(10)]:93  => 3
 get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
-get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar')  => 3
+get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar')  => 3
 
### `has(container, address, ...), has(lvalue)` @@ -100,13 +100,13 @@ of the existing elements. Use `replace` to remove and replace existing element. and merges keys from `value` with the compound tag under the path
-a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
-a = l(1, 2, 3); put(a, null, 4); a  => [1, 2, 3, 4]
-a = l(1, 2, 3); put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
-a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a  => [1, 2, 3, 4, 5, 6]
-a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a  => [1, 4, 5, 6, 2, 3]
-a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
-a = m(1,2,3,4); put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
+a = [1, 2, 3]; put(a, 1, 4); a  => [1, 4, 3]
+a = [1, 2, 3]; put(a, null, 4); a  => [1, 2, 3, 4]
+a = [1, 2, 3]; put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
+a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a  => [1, 2, 3, 4, 5, 6]
+a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a  => [1, 4, 5, 6, 2, 3]
+a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+a = {1,2,3,4}; put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
 tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag  => {BlockData:{Properties:[1,2,3,4]}}
 tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag  => {a:[{lvl:1},{lvl:1},{lvl:1}]}
 tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
@@ -225,7 +225,7 @@ Primarily to be used in higher order functions
 
 
 range(10)  => [...]
-l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+[range(10)]  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 reduce(range(10),_a+_, 0)  => 45
 range(5,10)  => [5, 6, 7, 8, 9]
diff --git a/docs/scarpet/language/LoopsAndHigherOrderFunctions.md b/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
index fa3ab931..95d018ed 100644
--- a/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
+++ b/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
@@ -124,12 +124,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound
 and `continue` have no sense and cannot be used inside of `expr` body.
 
 
-all([1,2,3], check_prime(_))  => 1
-all(neighbours(x,y,z), _=='stone')  => 1 // if all neighbours of [x, y, z] are stone
-map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
+all([1,2,3], check_prime(_))  => true
+all(neighbours(x,y,z), _=='stone')  => true // if all neighbours of [x, y, z] are stone
+map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
   => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]]
     // find all 3x3 bedrock structures in the top bedrock layer
-map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_);
+map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_);
         all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8),
    pos(_) )  => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]]
     // ditto, but requiring at most 7 bedrock block in the 18 blocks below them
diff --git a/docs/scarpet/language/Operators.md b/docs/scarpet/language/Operators.md
index 262f4cc2..4780124d 100644
--- a/docs/scarpet/language/Operators.md
+++ b/docs/scarpet/language/Operators.md
@@ -16,7 +16,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct
 1357-5 => 1352
 1357-'5' => 137
 3*'foo'-'o' => 'fff'
-l(1,3,5)+7 => l(8,10,12)
+[1,3,5]+7 => [8,10,12]
 
As you can see, values can behave differently when mixed with other types in the same expression. @@ -76,8 +76,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea any extra arguments.
-l(1,2,3) ~ 2  => 1
-l(1,2,3) ~ 4  => null
+[1,2,3] ~ 2  => 1
+[1,2,3] ~ 4  => null
 
 'foobar' ~ 'baz'  => null
 'foobar' ~ '.b'  => 'ob'
@@ -103,10 +103,10 @@ Or an example to find if a player has specific enchantment on a held axe (either
 
 
 global_get_enchantment(p, ench) -> (
-$   for(l('main','offhand'),
+$   for(['main','offhand'],
 $      holds = query(p, 'holds', _);
 $      if( holds,
-$         l(what, count, nbt) = holds;
+$         [what, count, nbt] = holds;
 $         if( what ~ '_axe' && nbt ~ ench,
 $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
 $         )
@@ -143,8 +143,8 @@ Examples:
 'foo'*3 => 'foofoofoo'
 'foofoofoo' / 3 => 'foo'
 'foofoofoo'-'o' => 'fff'
-l(1,2,3)+1  => l(2,3,4)
-b = l(100,63,100); b+l(10,0,10)  => l(110,63,110)
+[1,2,3]+1  => [2,3,4]
+b = [100,63,100]; b+[10,0,10]  => [110,63,110]
 {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
 
@@ -186,11 +186,12 @@ second operand is not necessary, it won't be evaluated, which means one can use case of success returns first positive operand (`||`) or last one (`&&`).
-true || false  => 1
-null || false => 0
-null != false || run('kill gnembon')  => 1 // gnembon survives
-null != false && run('kill gnembon')  => 0 // when cheats not allowed
-null != false && run('kill gnembon')  => 1 // gnembon dies, cheats allowed
+true || false  => true
+null || false => false
+false || null => null
+null != false || run('kill gnembon')  // gnembon survives
+null != false && run('kill gnembon')  // when cheats not allowed
+null != false && run('kill gnembon')  // gnembon dies, cheats allowed
 
### `Assignment Operators = <> +=` @@ -204,11 +205,11 @@ require rewriting of the array anyways.
 a = 5  => a == 5
-l(a,b,c) = l(3,4,5) => a==3, b==4, c==5
-l(minx,maxx) = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
-l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f)  => [3,4,5,0,1,2]
-a = l(1,2,3); a += 4  => [1,2,3,4]
-a = l(1,2,3,4); a = filter(a,_!=2)  => [1,3,4]
+[a,b,c] = [3,4,5] => a==3, b==4, c==5
+[minx,maxx] = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
+[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f]  => [3,4,5,0,1,2]
+a = [1,2,3]; a += 4  => [1,2,3,4]
+a = [1,2,3,4]; a = filter(a,_!=2)  => [1,3,4]
 
### `Unary Operators - +` @@ -226,10 +227,10 @@ Require a number, flips the sign. One way to assert it's a number is by crashing flips boolean condition of the expression. Equivalent of `bool(expr)==false`
-!true  => 0
-!false  => 1
-!null  => 1
-!5  => 0
-!l() => 1
-!l(null) => 0
+!true  => false
+!false  => true
+!null  => true
+!5  => false
+!l() => true
+!l(null) => false
 
diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 1f3768e6..bdc17d72 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -14,30 +14,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` -Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is -alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other places where boolean context can be used. This can be used in places where API functions return string values to -represent binary values +represent binary values.
-bool(pi) => 1
-bool(false) => 0
-bool('') => 0
-bool(l()) => 0
-bool(l('')) => 1
-bool('foo') => 1
-bool('false') => 0
-bool('nulL') => 0
-if('false',1,0) => 1
+bool(pi) => true
+bool(false) => false
+bool('') => false
+bool(l()) => false
+bool(l('')) => true
+bool('foo') => true
+bool('false') => false
+bool('nulL') => false
+if('false',1,0) => true
 
### `number(expr)` -Returns a numeric context of the expression. Can be used to read numbers from strings +Returns a numeric context of the expression. Can be used to read numbers from strings, or other types
-number(null) => null
+number(null) => 0
 number(false) => 0
+number(true) => 1
 number('') => null
 number('3.14') => 3.14
 number(l()) => 0
@@ -65,15 +66,15 @@ Supported types (with `"%"` syntax):
 *   `%%`: '%' character
 
 
-str(null) => null
-str(false) => 0
-str('') => null
-str('3.14') => 3.14
-str(l()) => 0
-str(l('')) => 1
-str('foo') => null
-str('3bar') => null
-str(2)+str(2) => 22
+str(null) => 'null'
+str(false) => 'false'
+str('') => ''
+str('3.14') => '3.14'
+str([]) => '[]'
+str(['']) => '[]'
+str('foo') => 'foo'
+str('3bar') => '3bar'
+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'
 
@@ -234,7 +235,7 @@ same random object. Scarpet keeps track of up to 1024 custom random number gener
 map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
-map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
+map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false]
 map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
 
diff --git a/docs/scarpet/language/VariablesAndConstants.md b/docs/scarpet/language/VariablesAndConstants.md index 2afb874d..a7657f4d 100644 --- a/docs/scarpet/language/VariablesAndConstants.md +++ b/docs/scarpet/language/VariablesAndConstants.md @@ -3,8 +3,8 @@ `scarpet` provides a number of constants that can be used literally in scripts * `null`: nothing, zilch, not even false -* `true`: pure true, or just 1 (one) -* `false`: false truth, or true falsth, 0 (zero) actually +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` * `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 * `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java index 2f21cb4e..4655f9d6 100644 --- a/src/main/java/carpet/script/language/Sys.java +++ b/src/main/java/carpet/script/language/Sys.java @@ -48,7 +48,7 @@ public class Sys { return (cc, tt) -> Value.FALSE; } } - Value retval = new NumericValue(v.getBoolean()); + Value retval = v.getBoolean()?Value.TRUE:Value.FALSE; return (cc, tt) -> retval; }); @@ -56,7 +56,9 @@ public class Sys { { if (v instanceof NumericValue) { - return v; + NumericValue num = (NumericValue)v; + if (num.isInteger()) return new NumericValue(num.getLong()); + return new NumericValue(num.getDouble()); } try { diff --git a/src/main/java/carpet/script/value/BooleanValue.java b/src/main/java/carpet/script/value/BooleanValue.java new file mode 100644 index 00000000..ea67f11b --- /dev/null +++ b/src/main/java/carpet/script/value/BooleanValue.java @@ -0,0 +1,58 @@ +package carpet.script.value; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.Tag; + +public class BooleanValue extends NumericValue +{ + public static final BooleanValue FALSE = new BooleanValue(false); + public static final BooleanValue TRUE = new BooleanValue(true); + + boolean boolValue; + private BooleanValue(boolean boolval) { + super(boolval); + boolValue = boolval; + } + + @Override + public String getString() { + return boolValue?"true":"false"; + } + + @Override + public String getPrettyString() { + return getString(); + } + + @Override + public String getTypeString() { + return "bool"; + } + + @Override + public Value clone() { + return new BooleanValue(boolValue); + } + + @Override + public int hashCode() { + return Boolean.hashCode(boolValue); + } + + @Override + public Tag toTag(boolean force) { + return ByteTag.of(boolValue); + } + + @Override + public JsonElement toJson() { + return new JsonPrimitive(boolValue); + } + + @Override + public boolean isInteger() { + return true; + } +} diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java index 7dcec512..1e5f08a0 100644 --- a/src/main/java/carpet/script/value/NullValue.java +++ b/src/main/java/carpet/script/value/NullValue.java @@ -9,6 +9,7 @@ import net.minecraft.nbt.Tag; public class NullValue extends NumericValue // TODO check nonsingleton code { + public static final NullValue NULL = new NullValue(); @Override public String getString() { @@ -32,7 +33,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code { return new NullValue(); } - public NullValue() {super(0.0D);} + private NullValue() {super(false);} @Override public boolean equals(final Object o) diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 5548d83f..573ed02c 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -15,12 +15,12 @@ import java.util.stream.Collectors; public abstract class Value implements Comparable, Cloneable { - public static NumericValue FALSE = new NumericValue(0); - public static NumericValue TRUE = new NumericValue(1); - public static NumericValue ZERO = FALSE; - public static NumericValue ONE = TRUE; + public static NumericValue FALSE = BooleanValue.FALSE; + public static NumericValue TRUE = BooleanValue.TRUE; + public static NumericValue ZERO = new NumericValue(0); + public static NumericValue ONE = new NumericValue(1); - public static NullValue NULL = new NullValue(); + public static NullValue NULL = NullValue.NULL; public String boundVariable; From 6d7f7a7bdc16dea3026c87d7e3bf31bf6ad4a257 Mon Sep 17 00:00:00 2001 From: Ghoulboy78 Date: Fri, 5 Mar 2021 22:24:42 +0100 Subject: [PATCH 27/87] improving prims not to calculate blocks unnecessarily (thx @Firigion) --- .../assets/carpet/scripts/shapes.scl | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index 440b0425..6da87888 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -70,9 +70,30 @@ draw_prism(args)->( positions = {}; hollow = fill_type =='hollow'; radius = rad+0.5; - for(range(height), - positions += fill_flat(pos, _, radius, is_square, orientation, !(_==0 || _==height-1) && hollow)//Always close ends off - ); + filled_circle_points = fill_flat(pos, 0, radius, is_square, orientation, false); //I dont actually need to generate all the points lol + offset_vector = if(orientation=='x', [1,0,0], orientation=='y', [0,1,0],[0,0,1]); + if(hollow, + hollow_circle_points = fill_flat(pos, 0, radius, is_square, orientation, true); + + for(filled_circle_points,//Always close ends off + positions+=_; + positions+=_+offset_vector*(height-1) + ); + + for(range(1, height-1),//cos Im adding ends as filled anyways + offset=_; + for(hollow_circle_points, + positions+= _ + offset_vector * offset + ) + ), + + for(range(height), + offset = _; + for(filled_circle_points, + positions += _ + offset_vector * offset + ) + ); + ) return(positions) ); From d97110a1ba5a42a40dfc0e0647dd3b2cd3f5f8da Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 6 Mar 2021 00:57:03 -0400 Subject: [PATCH 28/87] fixed use of client method when using item, entity and block tags. --- src/main/java/carpet/script/api/Inventories.java | 2 +- src/main/java/carpet/script/api/WorldAccess.java | 3 ++- src/main/java/carpet/script/value/EntityValue.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index fb4983ae..7fed53de 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -91,7 +91,7 @@ public class Inventories { Item item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()).getItem(); if (lv.size() == 1) { - Value ret = ListValue.wrap(tagManager.getItems().getTagsFor(item).stream().map(ValueConversions::of).collect(Collectors.toList())); + Value ret = ListValue.wrap(tagManager.getItems().getTags().entrySet().stream().filter(e -> e.getValue().contains(item)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(1).evalValue(c).getString(); diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index d15da361..47645714 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -1127,7 +1127,8 @@ public class WorldAccess { BlockArgument blockLocator = BlockArgument.findIn(cc, lv, 0, true); if (blockLocator.offset == lv.size()) { - Value ret = ListValue.wrap(tagManager.getBlocks().getTagsFor(blockLocator.block.getBlockState().getBlock()).stream().map(ValueConversions::of).collect(Collectors.toList())); + Block target = blockLocator.block.getBlockState().getBlock(); + Value ret = ListValue.wrap(tagManager.getBlocks().getTags().entrySet().stream().filter(e -> e.getValue().contains(target)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(blockLocator.offset).evalValue(c).getString(); diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 0e08f7a0..5172c4d9 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -429,7 +429,7 @@ public class EntityValue extends Value put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); put("scoreboard_tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); - put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTagsFor(e.getType()).stream().map(ValueConversions::of).collect(Collectors.toList()))); + put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTags().entrySet().stream().filter(entry -> entry.getValue().contains(e.getType())).map(entry -> ValueConversions.of(entry.getKey())).collect(Collectors.toList()))); // deprecated put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); From 38458a8e35cf70fe3dda7d16b4851fe2075bba00 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 7 Mar 2021 16:37:13 -0400 Subject: [PATCH 29/87] added create_datapack, check_hidden_dimensions and `world_dimensions` in system info. --- docs/scarpet/api/Auxiliary.md | 78 +++++++ docs/scarpet/api/BlocksAndWorldAccess.md | 4 +- docs/scarpet/resources/editors/idea/2.txt | 3 +- .../scarpet/resources/editors/npp/scarpet.xml | 2 +- .../fakes/MinecraftServerInterface.java | 2 + .../mixins/MinecraftServer_scarpetMixin.java | 8 + .../mixins/PlayerManager_coreMixin.java | 6 - .../mixins/ReloadCommand_reloadAppsMixin.java | 23 ++ .../java/carpet/script/api/Auxiliary.java | 209 +++++++++++++++++- .../java/carpet/script/api/WorldAccess.java | 1 + .../carpet/script/utils/InputValidator.java | 16 ++ .../java/carpet/script/utils/SystemInfo.java | 2 + .../java/carpet/script/utils/WorldTools.java | 2 +- .../java/carpet/script/value/MapValue.java | 7 +- src/main/resources/carpet.mixins.json | 1 + 15 files changed, 349 insertions(+), 15 deletions(-) create mode 100644 src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java create mode 100644 src/main/java/carpet/script/utils/InputValidator.java diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index cede1808..64b07dfd 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -385,6 +385,83 @@ Returns `true` if the file was saved successfully, `false` otherwise. Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. +### `create_datapack(name, data)` + +Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the +json files of the target pack. + +Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made. +Returns `false` if adding of the datapack wasn't successful. +Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in +reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` +command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`. + +Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically. + +Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads +dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified +file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects +(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more +experimental `check_hidden_dimensions()` after adding a datapack if needed. + +Synopsis: +
+script run create_datapack('foo', 
+{
+    'foo' -> {
+        'bar.json' -> {
+            'c' -> true,
+            'd' -> false,
+            'e' -> {'foo' -> [1,2,3]},
+            'a' -> 'foobar',
+            'b' -> 5
+        }
+    }
+})
+
+script run create_datapack('funky_world',  {
+    'data' -> {
+        'minecraft' -> {
+            'dimension' -> {
+                'custom_ow.json' -> { 
+                    'type' -> 'minecraft:the_end',
+                    'generator' -> {
+                        'biome_source' -> {
+                             'seed' -> 0,
+                             'large_biomes' -> false,
+                             'type' -> 'minecraft:vanilla_layered'
+                        },
+                        'seed' -> 0,
+                        'settings' -> 'minecraft:nether',
+                        'type' -> 'minecraft:noise'
+                    }
+                }
+            }
+        }
+    }
+});
+check_hidden_dimensions();  => ['funky_world']
+        
+
+ +### `enable_hidden_dimensions()` + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in ` (vanilla client limitation) +but all commands should work just fine with +the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced. +The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings +for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by +vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to +mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang +themselves, so that's about it. + ### `tick_time()` Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. @@ -502,6 +579,7 @@ Available options in the scarpet app space: Relevant world related properties * `world_name` - name of the world * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index a7419d0f..84f5afec 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -771,7 +771,9 @@ All generated structures will retain their properties, like mob spawning, howeve itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs, because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress. -### `custom_dimension(name, seed?)` +### (deprecated) `custom_dimension(name, seed?)` + +Deprecated by `create_datapack()` which can be used to setup custom dimensions Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided). diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 4186fa25..1c3251b3 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -17,9 +17,9 @@ brightness chunk_tickets convert_date crafting_remaining_item +create_datapack create_marker current_dimension -custom_dimension day_time delete_file destroy @@ -28,6 +28,7 @@ display_title draw_shape drop_item emitted_light +enable_hidden_dimensions encode_nbt entity_area entity_event diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index b7735dc6..5ae29a0f 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -55,7 +55,7 @@ set without_updates blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets - structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension + structure_eligibility structures structure_references set_structure reset_chunk reload_chunk create_datapack enable_hidden_dimensions add_chunk_ticket convert_date unix_time diff --git a/src/main/java/carpet/fakes/MinecraftServerInterface.java b/src/main/java/carpet/fakes/MinecraftServerInterface.java index 08fa24ae..a4422592 100644 --- a/src/main/java/carpet/fakes/MinecraftServerInterface.java +++ b/src/main/java/carpet/fakes/MinecraftServerInterface.java @@ -1,5 +1,6 @@ package carpet.fakes; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; @@ -13,4 +14,5 @@ public interface MinecraftServerInterface void forceTick(BooleanSupplier sup); LevelStorage.Session getCMSession(); Map, ServerWorld> getCMWorlds(); + ServerResourceManager getResourceManager(); } diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java index a4934a7f..1b71dfa9 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java @@ -2,6 +2,7 @@ package carpet.mixins; import carpet.fakes.MinecraftServerInterface; import carpet.helpers.TickSpeed; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; import net.minecraft.server.world.ServerWorld; @@ -44,6 +45,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut @Shadow @Final private Map, ServerWorld> worlds; + @Shadow private ServerResourceManager serverResourceManager; + @Override public void forceTick(BooleanSupplier isAhead) { @@ -58,6 +61,11 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut return session; } + @Override + public ServerResourceManager getResourceManager() { + return serverResourceManager; + } + @Override public Map, ServerWorld> getCMWorlds() { diff --git a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java index a272f7bd..de999990 100644 --- a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java @@ -25,10 +25,4 @@ public class PlayerManager_coreMixin CarpetServer.onPlayerLoggedIn(player); PLAYER_CONNECTS.onPlayerEvent(player); } - - @Inject(method = "onDataPacksReloaded", at = @At("HEAD")) - private void onResourcesRefreshedReloadCommand(CallbackInfo ci) - { - CarpetServer.onReload(server); - } } diff --git a/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java new file mode 100644 index 00000000..bf460603 --- /dev/null +++ b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java @@ -0,0 +1,23 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.command.ReloadCommand; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ReloadCommand.class) +public class ReloadCommand_reloadAppsMixin { + //method_13530(Lcom/mojang/brigadier/context/CommandContext;)I + // internal of register. + @SuppressWarnings("UnresolvedMixinReference") + @Inject(method = "method_13530", at = @At("TAIL")) + private static void onReload(CommandContext context, CallbackInfoReturnable cir) + { + // can't fetch here the reference to the server + CarpetServer.onReload(context.getSource().getMinecraftServer()); + } +} diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index a0268cc7..9bafb299 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -20,6 +20,7 @@ import carpet.script.argument.Vector3Argument; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.FixedCommandSource; +import carpet.script.utils.InputValidator; import carpet.script.utils.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; @@ -35,6 +36,10 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.utils.Messenger; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import net.minecraft.SharedConstants; import net.minecraft.block.BlockState; import net.minecraft.command.DataCommandStorage; import net.minecraft.entity.Entity; @@ -45,12 +50,18 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action; import net.minecraft.particle.ParticleEffect; +import net.minecraft.resource.DataPackSettings; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; @@ -62,13 +73,27 @@ import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.Util; +import net.minecraft.util.WorldSavePath; +import net.minecraft.util.dynamic.RegistryOps; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.DynamicRegistryManager; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.SaveProperties; import net.minecraft.world.World; +import net.minecraft.world.biome.source.BiomeAccess; +import net.minecraft.world.border.WorldBorderListener; +import net.minecraft.world.dimension.DimensionOptions; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.GeneratorOptions; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.level.UnmodifiableLevelProperties; +import net.minecraft.world.level.storage.LevelStorage; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -77,6 +102,13 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -95,7 +127,7 @@ import static java.lang.Math.min; public class Auxiliary { public static final String MARKER_STRING = "__scarpet_marker"; private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); - public static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); public static String recognizeResource(Value value, boolean isFloder) { @@ -838,7 +870,7 @@ public class Auxiliary { exception = e.getCause(); throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage()); } - Value parsedJson = gson.fromJson(json, Value.class); + Value parsedJson = GSON.fromJson(json, Value.class); if (parsedJson == null) retVal = Value.NULL; else @@ -877,7 +909,7 @@ public class Auxiliary { } else if (fdesc.getMiddle().equals("json")) { - List data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson())); + List data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson())); ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle()); success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data); } @@ -1048,8 +1080,179 @@ public class Auxiliary { if (old_nbt == null) return LazyValue.NULL; return (_c, _t) -> new NBTSerializableValue(old_nbt); }); + + // script run create_datapack('foo', {'foo' -> {'bar.json' -> {'c' -> true,'d' -> false,'e' -> {'foo' -> [1,2,3]},'a' -> 'foobar','b' -> 5}}}) + expression.addLazyFunction("create_datapack", 2, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + String origName = lv.get(0).evalValue(c).getString(); + String name = InputValidator.validateSimpleString(origName, true); + MinecraftServer server = cc.s.getMinecraftServer(); + for (String dpName : server.getDataPackManager().getNames()) + { + if (dpName.equalsIgnoreCase("file/"+name+".zip") || + dpName.equalsIgnoreCase("file/"+name)) + return LazyValue.NULL; + + } + Value dpdata = lv.get(1).evalValue(c); + if (!(dpdata instanceof MapValue)) + throw new InternalExpressionException("datapack data needs to be a valid map type"); + ResourcePackManager packManager = server.getDataPackManager(); + Path dbFloder = server.getSavePath(WorldSavePath.DATAPACKS); + Path packFloder = dbFloder.resolve(name+".zip"); + if (Files.exists(packFloder) || Files.exists(dbFloder.resolve(name))) return LazyValue.NULL; + Boolean [] successful = new Boolean[]{true}; + server.submitAndJoin( () -> + { + try { + //Files.createDirectory(packFloder); + try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + packFloder.toUri().toString()), ImmutableMap.of("create", "true"))) { + Path zipRoot = zipfs.getPath("/"); + zipValueToJson(zipRoot.resolve("pack.mcmeta"), MapValue.wrap( + ImmutableMap.of(StringValue.of("pack"), MapValue.wrap(ImmutableMap.of( + StringValue.of("pack_format"), new NumericValue(SharedConstants.getGameVersion().getPackVersion()), + StringValue.of("description"), StringValue.of(name), + StringValue.of("source"), StringValue.of("scarpet") + ))) + )); + walkTheDPMap((MapValue) dpdata, zipRoot); + } + packManager.scanPacks(); + ResourcePackProfile resourcePackProfile = packManager.getProfile("file/" + name + ".zip"); + if (resourcePackProfile == null || packManager.getEnabledProfiles().contains(resourcePackProfile)) { + throw new IOException(); + } + List list = Lists.newArrayList(packManager.getEnabledProfiles()); + resourcePackProfile.getInitialPosition().insert(list, resourcePackProfile, p -> p, false); + + + server.reloadResources(list.stream().map(ResourcePackProfile::getName).collect(Collectors.toList())). + exceptionally(exc -> { + successful[0] = false; + return null; + }).join(); + if (!successful[0]) { + throw new IOException(); + } + } catch (IOException e) + { + successful[0] = false; + try { + FileUtils.forceDelete(packFloder.toFile()); + } catch (IOException ignored) { + throw new InternalExpressionException("Failed to install a datapack and failed to clean up after it"); + } + + } + }); + return successful[0]?LazyValue.TRUE:LazyValue.FALSE; + }); + + expression.addLazyFunction("enable_hidden_dimensions", 0, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + // from minecraft.server.Main.main + MinecraftServer server = cc.s.getMinecraftServer(); + LevelStorage.Session session = ((MinecraftServerInterface)server).getCMSession(); + DataPackSettings dataPackSettings = session.getDataPackSettings(); + ResourcePackManager resourcePackManager = server.getDataPackManager(); + DataPackSettings dataPackSettings2 = MinecraftServer.loadDataPacks(resourcePackManager, dataPackSettings == null ? DataPackSettings.SAFE_MODE : dataPackSettings, false); + ServerResourceManager serverRM = ((MinecraftServerInterface)server).getResourceManager(); + ReloadableResourceManagerImpl resourceManager = (ReloadableResourceManagerImpl) serverRM.getResourceManager(); + + //believe the other one will fillup based on the datapacks only. + resourceManager.close(); + resourcePackManager.createResourcePacks().forEach(resourceManager::addPack); + + //not sure its needed, but doesn't seem to have a negative effect and might be used in some custom shtuff + serverRM.loadRegistryTags(); + + RegistryOps registryOps = RegistryOps.of(NbtOps.INSTANCE, serverRM.getResourceManager(), (DynamicRegistryManager.Impl) server.getRegistryManager()); + SaveProperties saveProperties = session.readLevelProperties(registryOps, dataPackSettings2); + if (saveProperties == null) return LazyValue.NULL; + //session.backupLevelDataFile(server.getRegistryManager(), saveProperties); // no need + + // MinecraftServer.createWorlds + // save properties should now contain dimension settings + GeneratorOptions generatorOptions = saveProperties.getGeneratorOptions(); + boolean bl = generatorOptions.isDebugWorld(); + long l = generatorOptions.getSeed(); + long m = BiomeAccess.hashSeed(l); + Map, ServerWorld> existing_worlds = ((MinecraftServerInterface)server).getCMWorlds(); + List addeds = new ArrayList<>(); + for (Map.Entry, DimensionOptions> entry : generatorOptions.getDimensions().getEntries()) { + RegistryKey registryKey = entry.getKey(); + if (!existing_worlds.containsKey(registryKey)) + { + addeds.add(ValueConversions.of(registryKey.getValue())); + RegistryKey registryKey2 = RegistryKey.of(Registry.DIMENSION, registryKey.getValue()); + DimensionType dimensionType3 = entry.getValue().getDimensionType(); + ChunkGenerator chunkGenerator3 = entry.getValue().getChunkGenerator(); + UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, saveProperties.getMainWorldProperties()); + ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false); + server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder())); + existing_worlds.put(registryKey2, serverWorld2); + } + } + Value ret = ListValue.wrap(addeds); + return (_c, _t) -> ret; + }); } + private static void zipValueToJson(Path path, Value output) throws IOException + { + JsonElement element = output.toJson(); + if (element == null) + throw new InternalExpressionException("Cannot interpret "+output.getPrettyString()+" as a json object"); + String string = GSON.toJson(element); + Files.createDirectories(path.getParent()); + BufferedWriter bufferedWriter = Files.newBufferedWriter(path); + Throwable incident = null; + try + { + bufferedWriter.write(string); + } + catch (Throwable shitHappened) + { + incident = shitHappened; + throw shitHappened; + } + finally + { + if (incident != null) { + try { + bufferedWriter.close(); + } catch (Throwable otherShitHappened) { + incident.addSuppressed(otherShitHappened); + } + } else { + bufferedWriter.close(); + } + } + } + + private static void walkTheDPMap(MapValue node, Path path) throws IOException + { + Map items = node.getMap(); + for (Map.Entry entry : items.entrySet()) + { + Value val = entry.getValue(); + String strkey = entry.getKey().getString(); + Path child = path.resolve(strkey); + if (strkey.endsWith(".json")) + { + zipValueToJson(child, val); + } + else + { + if (!(val instanceof MapValue)) throw new InternalExpressionException("Value of "+strkey+" should be a map"); + Files.createDirectory(child); + walkTheDPMap((MapValue) val, child); + } + } + } + + + private static Stat getStat(StatType type, Identifier id) { T key = type.getRegistry().get(id); diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index 47645714..dc801015 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -1383,6 +1383,7 @@ public class WorldAccess { { if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); CarpetContext cc = (CarpetContext)c; + cc.host.issueDeprecation("custom_dimension()"); String worldKey = lv.get(0).evalValue(c).getString(); Long seed = null; diff --git a/src/main/java/carpet/script/utils/InputValidator.java b/src/main/java/carpet/script/utils/InputValidator.java new file mode 100644 index 00000000..67405ab3 --- /dev/null +++ b/src/main/java/carpet/script/utils/InputValidator.java @@ -0,0 +1,16 @@ +package carpet.script.utils; + +import carpet.script.exception.InternalExpressionException; + +import java.util.Locale; + +public class InputValidator { + public static String validateSimpleString(String input, boolean strict) + { + String simplified = input.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9+_]", ""); + if (simplified.isEmpty() || (strict && !simplified.equals(input))) + throw new InternalExpressionException("simple name can only contain numbers, letter and _"); + return simplified; + } + +} diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 230fbac5..79dd6b1a 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -9,6 +9,7 @@ import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; +import carpet.script.value.ValueConversions; import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; @@ -49,6 +50,7 @@ public class SystemInfo { String tlf = serverPath.getName(nodeCount-2).toString(); return StringValue.of(tlf); }); + put("world_dimensions", c -> ListValue.wrap(c.s.getMinecraftServer().getWorldRegistryKeys().stream().map(k -> ValueConversions.of(k.getValue())).collect(Collectors.toList()))); put("game_difficulty", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getDifficulty().getName())); put("game_hardcore", c -> new NumericValue(c.s.getMinecraftServer().getSaveProperties().isHardcore())); put("game_storage_format", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getFormatName(c.s.getMinecraftServer().getSaveProperties().getVersion()))); diff --git a/src/main/java/carpet/script/utils/WorldTools.java b/src/main/java/carpet/script/utils/WorldTools.java index c32d94fc..8d6c4b27 100644 --- a/src/main/java/carpet/script/utils/WorldTools.java +++ b/src/main/java/carpet/script/utils/WorldTools.java @@ -157,5 +157,5 @@ public class WorldTools @Override public void stop() { } } - private static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); + public static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); } diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 81375bdc..10d11f24 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -7,6 +7,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -16,7 +17,7 @@ import java.util.stream.Collectors; public class MapValue extends AbstractListValue implements ContainerValueInterface { - private Map map; + private final Map map; private MapValue() { @@ -268,7 +269,9 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa public JsonElement toJson() { JsonObject jsonMap = new JsonObject(); - map.forEach((k, v) -> jsonMap.add(k.getString(), v.toJson())); + List keys = new ArrayList<>(map.keySet()); + Collections.sort(keys); + keys.forEach( k -> jsonMap.add(k.getString(), map.get(k).toJson())); return jsonMap; } } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index e6174d9d..1662b4f9 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -176,6 +176,7 @@ "PointOfInterest_scarpetMixin", "ServerWorld_scarpetMixin", "HungerManagerMixin_scarpetEventsMixin", + "ReloadCommand_reloadAppsMixin", "RecipeManager_scarpetMixin", "Ingredient_scarpetMixin", From b123899abd41318bb0b8613e86ec432e48ad65be Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 7 Mar 2021 22:14:35 +0100 Subject: [PATCH 30/87] Fix `run()` with a locked SettingsManager (#755) * Fix run() in locked SettingsManager * Fix small security issue while I'm at it * Notify command change --- src/main/java/carpet/CarpetSettings.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 6a1ab764..1435dc56 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -413,20 +413,26 @@ public class CarpetSettings private static class ModulePermissionLevel extends Validator { @Override public String validate(ServerCommandSource source, ParsedRule currentRule, String newValue, String string) { - CarpetSettings.runPermissionLevel = SettingsManager.getCommandLevel(newValue); + int permissionLevel = SettingsManager.getCommandLevel(newValue); + if (source != null && !source.hasPermissionLevel(permissionLevel)) + return null; + CarpetSettings.runPermissionLevel = permissionLevel; + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); return newValue; } @Override - public String description() { return "Also controls permission level of commands executed via `run()`";} + public String description() { return "When changing the rule, you must at least have the permission level you are trying to give it";} } @Rule( desc = "Enables restrictions for arbitrary code execution with scarpet", extra = { "Users that don't have this permission level", - "won't be able to load apps or /script run" + "won't be able to load apps or /script run.", + "It is also the permission level apps will", + "have when running commands with run()" }, - category = {COMMAND, SCARPET}, - validate = ModulePermissionLevel.class + category = {SCARPET}, + validate = {Validator._COMMAND_LEVEL_VALIDATOR.class, ModulePermissionLevel.class} ) public static String commandScriptACE = "ops"; From 8a1c9f2d0b451377fc5e4deea09472feaaa37db3 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 7 Mar 2021 22:16:20 +0100 Subject: [PATCH 31/87] Fix symbolic links (#754) --- .../java/carpet/script/CarpetScriptServer.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 8634ca00..fc9eb5e2 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -26,6 +26,7 @@ import net.minecraft.util.WorldSavePath; import net.minecraft.util.math.BlockPos; import java.io.IOException; +import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -136,7 +137,8 @@ public class CarpetScriptServer { try { Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); - Files.createDirectories(folder); + if (!Files.exists(folder)) + Files.createDirectories(folder); Optional scriptPath = Files.list(folder) .filter(script -> @@ -149,7 +151,8 @@ public class CarpetScriptServer if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); - Files.createDirectories(globalFolder); + if (!Files.exists(globalFolder)) + Files.createDirectories(globalFolder); scriptPath = Files.walk(globalFolder) .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") || (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl"))) @@ -194,7 +197,8 @@ public class CarpetScriptServer } try { Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); - Files.createDirectories(worldScripts); + if (!Files.exists(worldScripts)) + Files.createDirectories(worldScripts); Files.list(worldScripts) .filter(f -> f.toString().endsWith(".sc")) .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT))); @@ -202,8 +206,9 @@ public class CarpetScriptServer if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)) { Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); - Files.createDirectories(globalScripts); - Files.walk(globalScripts) + if (!Files.exists(globalScripts)) + Files.createDirectories(globalScripts); + Files.walk(globalScripts, FileVisitOption.FOLLOW_LINKS) .filter(f -> f.toString().endsWith(".sc")) .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT))); } From 36eccbc2df0f9880724ecc4a8267cb9f0e2d8416 Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 8 Mar 2021 00:09:02 +0100 Subject: [PATCH 32/87] Fix javadoc --- src/main/java/carpet/script/exception/ThrowStatement.java | 7 ++++--- src/main/java/carpet/script/exception/Throwables.java | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index b8cb4155..527befe1 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -39,9 +39,10 @@ public class ThrowStatement extends InternalExpressionException * Conveniently creates a value from the {@code value} String * to be used easily in Java code * @param message The message to display when not handled - * @param value A String that will be converted - * to a value to pass to {@code catch} - * blocks + * @param exception An {@link Exception} containing the inheritance data + * for this exception. When throwing from Java, + * those exceptions should be pre-registered and used + * instead of creating new ones each time. * @param parent This exception's data */ public ThrowStatement(String message, Exception exception) diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index ab6af103..ba42f993 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -65,6 +65,8 @@ public class Throwables { *

Parent will default to {@link Throwables#USER_DEFINED} if provided * parentValue meets Value#isNull, else it will create a new {@link Exception}, * with its parent being {@link Throwables#USER_DEFINED}. + *

If passed parent matches {@link Throwables#ERROR}, it will create an error + * exception, which cannot be caught. * @param value The {@link Value} of the exception * @param parentValue An optional {@link Value} matching the name of a parent exception. *
Accepts a {@link NullValue}, but not a {@code null} @@ -98,7 +100,7 @@ public class Throwables { } /** - * Checks whether the given filter matches an instance of this exception, including checking equality + * Checks whether the given filter matches an instance of this exception, by checking equality * with itself and possible parents. * @param filter The {@link Value} to check against * @return Whether or not the given value matches this exception's hierarchy From d48f78ff7b87c419935cb8a83c297d3d80da693d Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 8 Mar 2021 00:44:40 -0400 Subject: [PATCH 33/87] simplfy --- .../script/exception/ExpressionException.java | 8 + .../exception/ProcessedThrowStatement.java | 13 +- .../script/exception/ThrowStatement.java | 41 ++-- .../carpet/script/exception/Throwables.java | 183 ++++++++---------- .../carpet/script/language/ControlFlow.java | 39 ++-- 5 files changed, 118 insertions(+), 166 deletions(-) diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java index ec72fccb..ab9ec2e9 100644 --- a/src/main/java/carpet/script/exception/ExpressionException.java +++ b/src/main/java/carpet/script/exception/ExpressionException.java @@ -37,6 +37,14 @@ public class ExpressionException extends RuntimeException implements ResolvedExc context = c; } + public ExpressionException(Context c, Expression e, Tokenizer.Token t, Supplier messageSupplier, List stack) + { + super("Error"); + this.stack.addAll(stack); + lazyStacktrace = messageSupplier; + context = c; + } + private static final Fluff.TriFunction> errorMaker = (expr, /*Nullable*/ token, errmessage) -> { diff --git a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java index 003b71b0..977cf51f 100644 --- a/src/main/java/carpet/script/exception/ProcessedThrowStatement.java +++ b/src/main/java/carpet/script/exception/ProcessedThrowStatement.java @@ -6,14 +6,15 @@ import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer.Token; import carpet.script.value.FunctionValue; +import carpet.script.value.Value; public class ProcessedThrowStatement extends ExpressionException { - public final Throwables.Exception exception; - public final String message; + public final Throwables thrownExceptionType; + public final Value data; - public ProcessedThrowStatement(Context c, Expression e, Token token, String message, List stack, Throwables.Exception exception) { - super(c, e, token, "Unhandled exception: "+message, stack); - this.exception = exception; - this.message = message; + public ProcessedThrowStatement(Context c, Expression e, Token token, List stack, Throwables thrownExceptionType, Value data) { + super(c, e, token, () -> "Unhandled "+thrownExceptionType.getId()+" exception: "+data.getString(), stack); + this.thrownExceptionType = thrownExceptionType; + this.data = data; } } diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index 527befe1..01bf9ccf 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -3,35 +3,25 @@ package carpet.script.exception; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer.Token; +import carpet.script.value.StringValue; import carpet.script.value.Value; -import static carpet.script.exception.Throwables.Exception; - public class ThrowStatement extends InternalExpressionException { - private final Exception exception; - /** - * Creates a throw exception from a value. - * That value will also be used as the message.
- * To be used when throwing from Scarpet's {@code throw} function with a single argument - * @param value The value to pass - */ - public ThrowStatement(Value value) - { - this(value, value, Value.NULL); - } + private final Throwables thrownExceptionType; + private final Value exceptionData; /** * Creates a throw exception from a value, and assigns it a specified message. *

To be used when throwing from Scarpet's {@code throw} function - * @param message The message to display if uncaught - * @param value The value to pass - * @param parent A parent's name + * @param data The value to pass + * @param type Exception type */ - public ThrowStatement(Value message, Value value, Value parent) + public ThrowStatement(Value data, Throwables type) { - super(message.getString()); - exception = new Exception(value, parent); + super(type.getId()); + exceptionData = data; + thrownExceptionType = type; } /** @@ -39,20 +29,19 @@ public class ThrowStatement extends InternalExpressionException * Conveniently creates a value from the {@code value} String * to be used easily in Java code * @param message The message to display when not handled - * @param exception An {@link Exception} containing the inheritance data + * @param thrownExceptionType An {@link Throwables} containing the inheritance data * for this exception. When throwing from Java, - * those exceptions should be pre-registered and used - * instead of creating new ones each time. - * @param parent This exception's data + * those exceptions should be pre-registered. */ - public ThrowStatement(String message, Exception exception) + public ThrowStatement(String message, Throwables thrownExceptionType) { super(message); - this.exception = exception; + this.exceptionData = StringValue.of(message); + this.thrownExceptionType = thrownExceptionType; } @Override public ExpressionException promote(Context c, Expression e, Token token) { - return new ProcessedThrowStatement(c, e, token, getMessage(), stack, exception); + return new ProcessedThrowStatement(c, e, token, stack, thrownExceptionType, exceptionData); } } diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index ba42f993..136aa09e 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -1,129 +1,98 @@ package carpet.script.exception; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; - -import carpet.script.value.NullValue; -import carpet.script.value.StringValue; -import carpet.script.value.Value; +import java.util.Set; /** - * This class contains default Scarpet catchable exception's ids, as well as their inheritance and + * This class contains default Scarpet catchable types, as well as their inheritance and * methods to check whether filters are compatible with those. - * - * Exceptions here cannot be thrown as an actual {@link Throwable}, they are just part of the metadata for - * {@link ThrowStatement} and {@link ProcessedThrowStatement} */ public class Throwables { - protected static final Map byValue = new HashMap<>(); - public static final Exception EXCEPTION = register("exception", null); - public static final Exception VALUE_EXCEPTION = register("value_exception", EXCEPTION); - public static final Exception UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); - public static final Exception UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); - public static final Exception UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); - public static final Exception UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); - public static final Exception UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); - public static final Exception UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); - public static final Exception UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); - public static final Exception UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); - public static final Exception UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); - public static final Exception FILE_READ_EXCEPTION = register("file_read_exception", EXCEPTION); - public static final Exception NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); - public static final Exception JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); - public static final Exception USER_DEFINED = register("user_defined_exception", EXCEPTION); - protected static final Exception ERROR = register("error", null); - - private Throwables() {} - + private final String id; + private final Throwables parent; + + private static final Map byValue = new HashMap<>(); + private static final Map> handles = new HashMap<>(); + + public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null); + public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE); + public static final Throwables UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); + public static final Throwables FILE_READ_EXCEPTION = register("file_read_exception", THROWN_EXCEPTION_TYPE); + public static final Throwables NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); + public static final Throwables JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); + public static final Throwables USER_DEFINED = register("user_defined_exception", THROWN_EXCEPTION_TYPE); + /** * Creates an exception and registers it to be used as parent for * user defined exceptions in Scarpet's throw function. - *

Scarpet exceptions should, in general, have a top-level parent being {@link Throwables#EXCEPTION} - * @param value The value for the exception as a {@link String}. Will be converted into a {@link StringValue} + *

Scarpet exceptions should have a top-level parent being {@link Throwables#THROWN_EXCEPTION_TYPE} + * @param id The value for the exception as a {@link String}. * @param parent The parent of the exception being created, or null if top-level * @return The created exception */ - public static Exception register(String value, Exception parent) + public static Throwables register(String id, Throwables parent) { - Exception exc = new Exception(value, parent); - byValue.put(exc.getValue(), exc); + Throwables exc = new Throwables(id, parent); + byValue.put(id, exc); + Set itHandles = new HashSet<>(); + itHandles.add(exc); + while (parent!=null) + { + handles.get(parent.id).add(exc); + parent = parent.parent; + } + handles.put(id, itHandles); return exc; } - + /** - * The data of a {@link ThrowStatement} exception. Supports creating with inheritance and - * checking whether a filter is compatible with them. + * Creates a new exception. + *

Not suitable for creating exceptions that can't be caught. + * Use an {@link InternalExpressionException} for that + * @param id The exception's value as a {@link String} */ - public static class Exception { - private final Value value; - private final Exception parent; - private final boolean isError; - - /** - * Creates a new exception, with an optional parent. - *

To be used in Scarpet's throw command - *

Parent will default to {@link Throwables#USER_DEFINED} if provided - * parentValue meets Value#isNull, else it will create a new {@link Exception}, - * with its parent being {@link Throwables#USER_DEFINED}. - *

If passed parent matches {@link Throwables#ERROR}, it will create an error - * exception, which cannot be caught. - * @param value The {@link Value} of the exception - * @param parentValue An optional {@link Value} matching the name of a parent exception. - *
Accepts a {@link NullValue}, but not a {@code null} - */ - public Exception(Value value, Value parentValue) - { - if (byValue.containsKey(value)) - throw new InternalExpressionException("Exception value can't be the same as existing exception. Use it as parent instead"); - this.value = value; - if (!parentValue.isNull()) - { - Exception possibleParent = byValue.get(parentValue); - parent = possibleParent == null ? new Exception(parentValue, Value.NULL) : possibleParent; - } else - parent = USER_DEFINED; - isError = this.parent == ERROR; - } - - /** - * Creates a new exception. - *

Not suitable for creating exceptions that can't be caught. - * Use an {@link InternalExpressionException} for that - * @param value The exception's value as a {@link String} - * @param parent The parent exception, or null if no parent is available - */ - protected Exception(String value, Exception parent) - { - this.value = StringValue.of(value); - this.parent = parent; - this.isError = false; - } - - /** - * Checks whether the given filter matches an instance of this exception, by checking equality - * with itself and possible parents. - * @param filter The {@link Value} to check against - * @return Whether or not the given value matches this exception's hierarchy - */ - public boolean isInstance(Value filter) { - return getValue().equals(filter) || (parent != null && parent.isInstance(filter)); - } - - /** - * Returns the {@link Value} provided when creating this exception - * @return The {@link Value} of this exception - */ - public Value getValue() { - return value; - } - - /** - * Returns whether this exception has been defined as an error. - *

Error exceptions cannot be caught in Scarpet - * @return Whether this exception has been defined as an error - */ - public boolean isError() { - return isError; - } + protected Throwables(String id, Throwables parent) + { + this.id = id; + this.parent = parent; + } + + public static Throwables getTypeForException(String type) + { + Throwables properType = byValue.get(type); + if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type); + return properType; + } + + + /** + * Checks whether the given filter matches an instance of this exception, by checking equality + * with itself and possible parents. + * @param filter The type to check against + * @return Whether or not the given value matches this exception's hierarchy + */ + public boolean isRelevantFor(String filter) + { + Set relevantSet = handles.get(filter); + if (relevantSet == null) throw new InternalExpressionException("Unknown exception type: "+filter); + return relevantSet.contains(this); + } + + /** + * Returns the throwable type + * @return The type of this exception + */ + public String getId() { + return id; } } diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index b704a908..f5173f26 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -8,7 +8,6 @@ import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; -import carpet.script.value.StringValue; import carpet.script.value.Value; public class ControlFlow { @@ -50,15 +49,13 @@ public class ControlFlow { switch (lv.size()) { case 0: - throw new ThrowStatement(new StringValue("No further information"), Value.NULL, Value.NULL); + throw new ThrowStatement(Value.NULL, Throwables.USER_DEFINED); case 1: - throw new ThrowStatement(lv.get(0).evalValue(c)); + throw new ThrowStatement(lv.get(0).evalValue(c), Throwables.USER_DEFINED ); case 2: - throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), Value.NULL); - case 3: - throw new ThrowStatement(lv.get(1).evalValue(c), lv.get(0).evalValue(c), lv.get(2).evalValue(c)); + throw new ThrowStatement(lv.get(1).evalValue(c), Throwables.getTypeForException(lv.get(0).evalValue(c).getString())); default: - throw new InternalExpressionException("throw() can't accept more than 3 parameters"); + throw new InternalExpressionException("throw() can't accept more than 2 parameters"); } }); @@ -73,55 +70,43 @@ public class ControlFlow { } catch (ProcessedThrowStatement ret) { - if (ret.exception.isError()) - throw ret; if (lv.size() == 1) { - if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) + if (ret.thrownExceptionType != Throwables.USER_DEFINED) throw ret; return (c_, t_) -> Value.NULL; } - if (lv.size() == 0 || lv.size() % 2 == 0) + if (lv.size() > 3 && lv.size() % 2 == 0) { - throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch_expr or a number of pairs of filters and catch_expr"); + throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch expression for user thrown exceptions, or a number of pairs of filters and catch expressions"); } Value val = null; // This is always assigned at some point, just the compiler doesn't know LazyValue __ = c.getVariable("_"); - c.setVariable("_", (__c, __t) -> ret.exception.getValue().reboundedTo("_")); - LazyValue __msg = c.getVariable("_msg"); - c.setVariable("_msg", (__c, __t) -> StringValue.of(ret.message).reboundedTo("_msg")); - - boolean handled; + c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_")); + if (lv.size() == 2) { - if (!ret.exception.isInstance(Throwables.USER_DEFINED.getValue())) - handled = false; - else { + if (ret.thrownExceptionType == Throwables.USER_DEFINED) val = lv.get(1).evalValue(c, t); - handled = true; - } } else { int pointer = 1; - handled = false; while (pointer < lv.size() -1) { - if (ret.exception.isInstance(lv.get(pointer).evalValue(c))) + if (ret.thrownExceptionType.isRelevantFor(lv.get(pointer).evalValue(c).getString())) { val = lv.get(pointer + 1).evalValue(c, t); - handled = true; break; } pointer += 2; } } c.setVariable("_", __); - c.setVariable("_msg", __msg); - if (!handled) + if (val == null) // not handled { throw ret; } From d72a617290235d6dd321816214ba6f8ed33a271b Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 04:57:23 -0400 Subject: [PATCH 34/87] added trace information to thrown exceptions and clean some of the docs --- .../language/FunctionsAndControlFlow.md | 104 ++++++++++-------- src/main/java/carpet/script/Tokenizer.java | 1 + .../script/exception/ExpressionException.java | 10 +- .../carpet/script/exception/Throwables.java | 32 +++--- .../carpet/script/language/ControlFlow.java | 31 +++++- 5 files changed, 111 insertions(+), 67 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 79c931a3..73d5d1fc 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -234,61 +234,71 @@ returns everywhere, but it would often lead to a messy code. It terminates entire program passing `expr` as the result of the program execution, or null if omitted. -### `try(expr, id_filter, catch_expr(_, _msg), id_filter2?, catch_expr2(_, _msg), ...)` +### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)` -`try` function evaluates expression, and continues further unless an exception is thrown anywhere inside `expr`, -it being produced by `throw` or a catchable exception being thrown by Carpet. In that case the `catch_expr` is evaluated with -`_` set to the id of the exception and `_msg` set to the exception message. Only exceptions with their or their parent's id(recursively) equal -to a provided `id_filter` will be caught, and any other exception will continue up the stack. You can specify multiple filters -and their respective `catch_expr` to be able to catch different exceptions within the same code. When an exception occurs, the script -will try to find an equality with every provided filter in order. Therefore, even if the caught exception matches multiple filters, only -the first matching block will be executed. It is recommended to filter the exceptions to catch to be able to distinguish from an exception -that is known that may happen from one that shouldn't. +`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be +thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form +catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`, +or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first +applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only +the first matching block will be executed. -This mechanism allows to terminate large portion of a convoluted call stack and continue program execution. +Catch expressions are evaluated with +`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and +column positions), call stack and local +variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type. + -The `try` function allows you to catch some Scarpet exceptions for those cases when trying to get things like items, -blocks, biomes or dimensions from registries, that may have been modified by datapacks, resourcepacks or other mods, or when an error that -is out of the scope of the programmer occurs, such as problems when reading files. +You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching +exceptions is typically much more expensive comparing to not throwing them. -Deprecated usages: -- `try(expr)`: Filter the exception you are looking for, and provide something as the `catch_expr` (use `null` if you have nothing to provide). - Example replacement: `try(expr, 'exception', null)` -- `try(expr, catch_expr)`: Filter the exception you are looking for. +The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid +blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods, +or when an error is outside of the programmers scope, such as problems when reading or decoding files. -In this documentation, those are documented in at least most of the functions that throw them, and the current exception -hierarchy is the following: -- `exception`: This is the base exception. Every exception has this as its top-level parent - - `value_exception`: This is the parent for any exception that occurs due to a provided value for something not being present in the game - - `unknown_item`: Happens when a specified item doesn't exist - - `unknown_block`: Happens when a specified block doesn't exist - - `unknown_biome`: Happens when a specified biome doesn't exist - - `unknown_sound`: Happens when a specified sound doesn't exist - - `unknown_particle`: Happens when a specified particle doesn't exist - - `unknown_poi_type`: Happens when a specified POI type doesn't exist - - `unknown_dimension`: Happens when a specified dimension doesn't exist - - `unknown_structure`: Happens when a specified structure doesn't exist - - `unknown_criterion`: Happens when a specified scoreboard criterion doesn't exist - - `file_read_exception`: This is the parent for any exception that occurs due to an error while reading a file, including appdata load - - `nbt_read_exception`: Happens when there is an exception while reading an NBT file - - `json_read_exception`: Happens when there is an exception while reading a JSON file - - `user_defined_exception`: This is the parent for any exception thrown by the below `throw` function, except for those that explicitly declare - their parent to be one of the above +This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function: +- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught, +but like everywhere else, doing that sounds like a bad idea. + - `value_exception`: This is the parent for any exception that occurs due to an + incorrect argument value provided to a built-in function + - `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`, + `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific + errors thrown when a specified internal name does not exist or is invalid. + - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. + - `nbt_read_error`: Incorrect input NBT file. + - `json_read_error`: Incorrect input JSON data. + - `user_exception`: Exception thrown by default with `throw` function. + +Synopsis: +

+inner_call() ->
+(
+   aaa = 'booyah';
+   try(
+      for (range(10), item_tags('stick'+_*'k'));
+   ,
+      print(_trace) // not caught, only catching user_exceptions
+   )
+);
 
-### `throw(id, message?, parent?)`
+outer_call() -> 
+( 
+   try(
+      inner_call()
+   , 'exception', // catching everything
+      print(_trace)
+   ) 
+);
+
+Producing: +``` +{stack: [[, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]} +``` -Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will pass `null` -as the value to the `catch_expr`. `message` is the message to show to chat or console in case the `throw` is -unhandled. +### `throw(value?)` `throw(type, value)` -By default, the parent of any exception thrown by this function is `user_defined_exception`. -You can specify the `parent` to be either an already existing exception from the list above, in which case your exception -will not inherit from `user_defined_exception`, or specify a custom one, in which case your exception will inherit from -`exception` > `user_defined_exception` > `parent` > `id`. - -If you don't want your exception to be handled, you can use `error` as the `parent` to throw an error which cannot be handled. - -Deprecated usage: `throw()` (without id): Specify at least the id of your exception +Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw `user_exception` +passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet. ### `if(cond, expr, cond?, expr?, ..., default?)` diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java index 30fac871..24a92df1 100644 --- a/src/main/java/carpet/script/Tokenizer.java +++ b/src/main/java/carpet/script/Tokenizer.java @@ -406,6 +406,7 @@ public class Tokenizer implements Iterator public int pos; public int linepos; public int lineno; + public static final Token NONE = new Token(); public Token morphedInto(TokenType newType, String newSurface) { Token created = new Token(); diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java index ab9ec2e9..c62e3db7 100644 --- a/src/main/java/carpet/script/exception/ExpressionException.java +++ b/src/main/java/carpet/script/exception/ExpressionException.java @@ -14,26 +14,29 @@ import java.util.function.Supplier; public class ExpressionException extends RuntimeException implements ResolvedException { public final Context context; + public final Tokenizer.Token token; public final List stack = new ArrayList<>(); private final Supplier lazyStacktrace; private String cachedMessage = null; public ExpressionException(Context c, Expression e, String message) { - this(c, e, null, message); + this(c, e, Tokenizer.Token.NONE, message); } public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message) { super("Error"); lazyStacktrace = () -> makeMessage(c, e, t, message); + token = t; context = c; } public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack) { super("Error"); this.stack.addAll(stack); - lazyStacktrace = () -> makeMessage(c, e, t, message); + lazyStacktrace = () -> makeMessage(c, e, t, message); + token = t; context = c; } @@ -41,7 +44,8 @@ public class ExpressionException extends RuntimeException implements ResolvedExc { super("Error"); this.stack.addAll(stack); - lazyStacktrace = messageSupplier; + lazyStacktrace = () -> makeMessage(c, e, t, messageSupplier.get()); + token = t; context = c; } diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 136aa09e..589ea740 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -13,24 +13,24 @@ public class Throwables { private final String id; private final Throwables parent; - private static final Map byValue = new HashMap<>(); + private static final Map byId = new HashMap<>(); private static final Map> handles = new HashMap<>(); public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null); public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE); - public static final Throwables UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); - public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); - public static final Throwables FILE_READ_EXCEPTION = register("file_read_exception", THROWN_EXCEPTION_TYPE); - public static final Throwables NBT_READ = register("nbt_read_exception", FILE_READ_EXCEPTION); - public static final Throwables JSON_READ = register("json_read_exception", FILE_READ_EXCEPTION); - public static final Throwables USER_DEFINED = register("user_defined_exception", THROWN_EXCEPTION_TYPE); + public static final Throwables UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_POI_TYPE = register("unknown_poi_type", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION); + public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION); + public static final Throwables IO_EXCEPTION = register("io_exception", THROWN_EXCEPTION_TYPE); + public static final Throwables NBT_READ = register("nbt_read_error", IO_EXCEPTION); + public static final Throwables JSON_READ = register("json_read_error", IO_EXCEPTION); + public static final Throwables USER_DEFINED = register("user_exception", THROWN_EXCEPTION_TYPE); /** * Creates an exception and registers it to be used as parent for @@ -43,7 +43,7 @@ public class Throwables { public static Throwables register(String id, Throwables parent) { Throwables exc = new Throwables(id, parent); - byValue.put(id, exc); + byId.put(id, exc); Set itHandles = new HashSet<>(); itHandles.add(exc); while (parent!=null) @@ -69,7 +69,7 @@ public class Throwables { public static Throwables getTypeForException(String type) { - Throwables properType = byValue.get(type); + Throwables properType = byId.get(type); if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type); return properType; } diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index f5173f26..7972d582 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -8,7 +8,14 @@ import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ProcessedThrowStatement; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; +import carpet.script.value.ListValue; +import carpet.script.value.MapValue; +import carpet.script.value.NumericValue; +import carpet.script.value.StringValue; import carpet.script.value.Value; +import com.google.common.collect.ImmutableMap; + +import java.util.stream.Collectors; public class ControlFlow { public static void apply(Expression expression) // public just to get the javadoc right @@ -85,6 +92,25 @@ public class ControlFlow { LazyValue __ = c.getVariable("_"); c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_")); + LazyValue _trace = c.getVariable("_trace"); + c.setVariable("_trace", (__c, __t) -> MapValue.wrap(ImmutableMap.of( + StringValue.of("stack"), ListValue.wrap(ret.stack.stream().map(f -> ListValue.of( + StringValue.of(f.getModule().getName()), + StringValue.of(f.getString()), + NumericValue.of(f.getToken().lineno+1), + NumericValue.of(f.getToken().linepos+1) + )).collect(Collectors.toList())), + + StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().collect(Collectors.toMap( + e -> StringValue.of(e.getKey()), + e -> e.getValue().evalValue(ret.context) + ))), + StringValue.of("token"), ListValue.of( + StringValue.of(ret.token.surface), + NumericValue.of(ret.token.lineno+1), + NumericValue.of(ret.token.linepos+1) + ) + ))); if (lv.size() == 2) { @@ -105,7 +131,10 @@ public class ControlFlow { } } c.setVariable("_", __); - + if (_trace != null) + c.setVariable("_trace", _trace); + else + c.delVariable("_trace"); if (val == null) // not handled { throw ret; From 2d1b53258c13a8b4638b8974a86c7a21157f0550 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 13:29:20 -0400 Subject: [PATCH 35/87] simplify error messages --- src/main/java/carpet/script/api/Auxiliary.java | 14 +++++++++----- src/main/java/carpet/script/api/Inventories.java | 2 +- src/main/java/carpet/script/api/Scoreboards.java | 2 +- src/main/java/carpet/script/api/WorldAccess.java | 12 ++++++------ .../carpet/script/exception/ThrowStatement.java | 8 ++++---- .../java/carpet/script/utils/ShapeDispatcher.java | 2 +- src/main/java/carpet/script/value/BlockValue.java | 2 +- .../carpet/script/value/NBTSerializableValue.java | 2 +- .../java/carpet/script/value/ValueConversions.java | 2 +- 9 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 63ebc142..39192581 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -162,10 +162,11 @@ public class Auxiliary { { expression.addLazyFunction("sound", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; - Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString()); + String rawString = lv.get(0).evalValue(c).getString(); + Identifier soundName = new Identifier(rawString); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); if (Registry.SOUND_EVENT.get(soundName) == null) - throw new ThrowStatement("No such sound: "+soundName.getPath(), Throwables.UNKNOWN_SOUND); + throw new ThrowStatement(rawString, Throwables.UNKNOWN_SOUND); float volume = 1.0F; float pitch = 1.0F; SoundCategory mixer = SoundCategory.MASTER; @@ -861,7 +862,7 @@ public class Auxiliary { state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight()); } catch (CrashException e) { - throw new ThrowStatement("Couldn't read NBT data", Throwables.NBT_READ); + throw new ThrowStatement((fdesc.getRight()?"shared/":"")+ fdesc.getLeft(), Throwables.NBT_READ); } if (state == null) return LazyValue.NULL; @@ -879,7 +880,10 @@ public class Auxiliary { Throwable exc = e; if(e.getCause() != null) exc = e.getCause(); - throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), Throwables.JSON_READ); + throw new ThrowStatement(MapValue.wrap(ImmutableMap.of( + StringValue.of("error"), StringValue.of(exc.getMessage()), + StringValue.of("path"), StringValue.of((fdesc.getRight()?"shared/":"")+ fdesc.getLeft()) + )), Throwables.JSON_READ); } Value parsedJson = GSON.fromJson(json, Value.class); if (parsedJson == null) @@ -973,7 +977,7 @@ public class Auxiliary { state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared); } catch (CrashException e) { - throw new ThrowStatement("Failed to read App data", Throwables.NBT_READ); + throw new ThrowStatement("app data", Throwables.NBT_READ); } if (state == null) return (cc, tt) -> Value.NULL; diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index e3ad4353..96dd35e1 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -193,7 +193,7 @@ public class Inventories { String itemStr = lv.get(0).evalValue(c).getString(); Item item; Identifier id = new Identifier(itemStr); - item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement("Incorrect item: "+itemStr, Throwables.UNKNOWN_ITEM)); + item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement(itemStr, Throwables.UNKNOWN_ITEM)); if (!item.hasRecipeRemainder()) return LazyValue.NULL; Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder()))); diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index c58f7c10..f0a2e598 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -133,7 +133,7 @@ public class Scoreboards { criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null); if (criterion==null) { - throw new ThrowStatement("Unknown scoreboard criterion: "+critetionName, Throwables.UNKNOWN_CRITERION); + throw new ThrowStatement(critetionName, Throwables.UNKNOWN_CRITERION); } } diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index 36652027..ff8640fb 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -339,7 +339,7 @@ public class WorldAccess { if (!"any".equals(poiType)) { PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type", Throwables.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI_TYPE)); condition = (tt) -> tt == type; } if (locator.offset + 2 < lv.size()) @@ -394,7 +394,7 @@ public class WorldAccess { } String poiTypeString = poi.getString().toLowerCase(Locale.ROOT); PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString)) - .orElseThrow(() -> new ThrowStatement("Unknown POI type: "+poiTypeString, Throwables.UNKNOWN_POI_TYPE)); + .orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI_TYPE)); int occupancy = 0; if (locator.offset + 1 < lv.size()) { @@ -874,7 +874,7 @@ public class WorldAccess { playerBreak = true; String itemString = val.getString(); item = Registry.ITEM.getOrEmpty(new Identifier(itemString)) - .orElseThrow(() -> new ThrowStatement("Incorrect item: " + itemString, Throwables.UNKNOWN_ITEM)); + .orElseThrow(() -> new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM)); } } CompoundTag tag = null; @@ -1182,7 +1182,7 @@ public class WorldAccess { String biomeName = lv.get(locator.offset+0).evalValue(c).getString(); // from locatebiome command code Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName)) - .orElseThrow(() -> new ThrowStatement("Unknown biome: "+biomeName, Throwables.UNKNOWN_BIOME)); + .orElseThrow(() -> new ThrowStatement(biomeName, Throwables.UNKNOWN_BIOME)); boolean doImmediateUpdate = true; if (lv.size() > locator.offset+1) { @@ -1252,7 +1252,7 @@ public class WorldAccess { { String reqString = requested.getString(); structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString)) - .orElseThrow(() -> new ThrowStatement("Unknown structure: " + reqString, Throwables.UNKNOWN_STRUCTURE)); + .orElseThrow(() -> new ThrowStatement(reqString, Throwables.UNKNOWN_STRUCTURE)); } if (lv.size() > locator.offset+1) { @@ -1332,7 +1332,7 @@ public class WorldAccess { throw new InternalExpressionException("'set_structure requires at least position and a structure name"); String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT); ConfiguredStructureFeature configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos); - if (configuredStructure == null) throw new ThrowStatement("Unknown structure: "+structureName, Throwables.UNKNOWN_STRUCTURE); + if (configuredStructure == null) throw new ThrowStatement(structureName, Throwables.UNKNOWN_STRUCTURE); // good 'ol pointer Value[] result = new Value[]{Value.NULL}; // technically a world modification. Even if we could let it slide, we will still park it diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java index 01bf9ccf..74dfa343 100644 --- a/src/main/java/carpet/script/exception/ThrowStatement.java +++ b/src/main/java/carpet/script/exception/ThrowStatement.java @@ -29,15 +29,15 @@ public class ThrowStatement extends InternalExpressionException * Conveniently creates a value from the {@code value} String * to be used easily in Java code * @param message The message to display when not handled - * @param thrownExceptionType An {@link Throwables} containing the inheritance data + * @param type An {@link Throwables} containing the inheritance data * for this exception. When throwing from Java, * those exceptions should be pre-registered. */ - public ThrowStatement(String message, Throwables thrownExceptionType) + public ThrowStatement(String message, Throwables type) { - super(message); + super(type.getId()); this.exceptionData = StringValue.of(message); - this.thrownExceptionType = thrownExceptionType; + this.thrownExceptionType = type; } @Override diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 84aab23a..87a153f2 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -158,7 +158,7 @@ public class ShapeDispatcher } catch (CommandSyntaxException e) { - throw new ThrowStatement("No such particle: "+name, Throwables.UNKNOWN_PARTICLE); + throw new ThrowStatement(name, Throwables.UNKNOWN_PARTICLE); } particleCache.put(name, particle); return particle; diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index fe26f57f..27d21cdd 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -76,7 +76,7 @@ public class BlockValue extends Value catch (CommandSyntaxException ignored) { } - throw new ThrowStatement("Cannot parse block: "+str, Throwables.UNKNOWN_BLOCK); + throw new ThrowStatement(str, Throwables.UNKNOWN_BLOCK); } public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos) diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 11595d06..9c26f252 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -299,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } catch (CommandSyntaxException e) { - throw new ThrowStatement("Incorrect item: "+itemString, Throwables.UNKNOWN_ITEM); + throw new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM); } } diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index 320916ae..ba5bbf24 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -160,7 +160,7 @@ public class ValueConversions } } if (dim == null) - throw new ThrowStatement("Incorrect dimension string: "+dimString, Throwables.UNKNOWN_DIMENSION); + throw new ThrowStatement(dimString, Throwables.UNKNOWN_DIMENSION); return server.getWorld(dim); } } From 72af3f732e2b8fe3f0ba3dfdae6095e07502db0b Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 13:51:52 -0400 Subject: [PATCH 36/87] cleaned docs --- docs/scarpet/api/Auxiliary.md | 8 ++++---- docs/scarpet/api/BlocksAndWorldAccess.md | 18 ++++++++---------- .../java/carpet/script/api/WorldAccess.java | 8 +++----- .../carpet/script/exception/Throwables.java | 2 +- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index d9209f5a..1a7d3c33 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -328,8 +328,8 @@ write is beneficial for writing speed. To send multiple packs of data, either pr third argument. Throws: -- `nbt_read_exception`: When failed to read NBT file -- `json_read_exception`: When failed to read JSON file. The `_msg` variable will contain details about the problem +- `nbt_read_error`: When failed to read NBT file. +- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem.
 write_file('foo', 'shared_text, ['one', 'two']);
@@ -382,7 +382,7 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
 
 You can use app data to save non-vanilla information separately from the world and other scripts.
 
-Throws `nbt_read_exception` if failed to read app data.
+Throws `nbt_read_error` if failed to read app data.
 
 ### `store_app_data(tag)`
 
@@ -538,7 +538,7 @@ Evaluates the expression `expr` with different dimension execution context. `smt
 world-localized block, so not `block('stone')`, or a string representing a dimension like:
  `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
  
-Throws `unknown_dimension` if given a dimension as a String and it can't be found.
+Throws `unknown_dimension` if provided dimension can't be found.
  
 ### `view_distance()`
 Returns the view distance of the server.
diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index fdc3e82c..3f27bb4d 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -21,7 +21,7 @@ when it was used. Block values passed in various places like `scan` functions, e
 its properties are needed. This means that if the block at the location changes before its queried in the program this 
 might result in getting the later state, which might not be desired. Consider the following example:
 
-Throws `unknown_block` if block from `state` doesn't exist.
+Throws `unknown_block` if provided input is not valid.
 
 
set(10,10,10,'stone');
 scan(10,10,10,0,0,0, b = _);
@@ -52,7 +52,7 @@ properties that the original source block may have contained.
 
 The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
 
-Throws `unknown_block` if `block` doesn't exist.
+Throws `unknown_block` if provided block to set is not valid
 
 
 set(0,5,0,'bedrock')  => bedrock
@@ -152,7 +152,7 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
 Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial 
 markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
 
-Throws `unknown_poi_type` if POI type doesn't exist 
+Throws `unknown_poi` if the provided point of interest doesn't exist 
 
 ### `set_biome(pos, biome_name, update=true)`
 
@@ -164,7 +164,7 @@ is only effective if you set it at y=0, and affects the entire column of. In the
 specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
 noise).
 
-Throws `unknown_biome` if biome doesn't exist.
+Throws `unknown_biome` if the `biome_name` doesn't exist.
 
 ### `update(pos)`
 
@@ -283,7 +283,7 @@ back in state definition in various applications where block properties are requ
 
 `block_state` can also accept block names as input, returning block's default state.
 
-Throws `unknown_block` if `block` comes from a String and doesn't exist.
+Throws `unknown_block` if the provided input is not valid.
 
 
 set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z)  => {waterlogged: false, half: top, open: false, ...}
@@ -305,7 +305,7 @@ Without arguments, returns list of available tags, with block supplied (either b
 of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong 
 to this tag, and `true` if the block belongs to the tag.
 
-Throws `unknown_block` if `block` comes from a String and doesn't exist
+Throws `unknown_block` if `block` doesn't exist
 
 ### `block_data(pos)`
 
@@ -341,9 +341,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
 
 Without arguments, returns the list of biomes in the world.
 
-With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid. 
+With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. 
 
-With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include:
+With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include:
 * `'top_material'`: unlocalized block representing the top surface material
 * `'under_material'`: unlocalized block representing what sits below topsoil
 * `'category'`: the parent biome this biome is derived from. Possible values include:
@@ -364,8 +364,6 @@ and values < 0, below sea level.
 * `'features'`: list of features that generate in the biome, grouped by generation steps
 * `'structures'`: list of structures that generate in the biome.
 
-Throws `unknown_block` if `block` comes from a String and doesn't exist. 
-
 ### `solid(pos)`
 
 Boolean function, true if the block is solid.
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index ff8640fb..46b1cbbe 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -61,7 +61,6 @@ import net.minecraft.item.SwordItem;
 import net.minecraft.item.TridentItem;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.Tag;
-import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.world.ChunkTicket;
 import net.minecraft.server.world.ChunkTicketType;
@@ -108,7 +107,6 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.BiFunction;
-import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -339,7 +337,7 @@ public class WorldAccess {
                 if (!"any".equals(poiType))
                 {
                     PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType))
-                            .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI_TYPE));
+                            .orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI));
                     condition = (tt) -> tt == type;
                 }
                 if (locator.offset + 2 < lv.size())
@@ -394,7 +392,7 @@ public class WorldAccess {
             }
             String poiTypeString = poi.getString().toLowerCase(Locale.ROOT);
             PointOfInterestType type =  Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString))
-            		.orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI_TYPE));
+            		.orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI));
             int occupancy = 0;
             if (locator.offset + 1 < lv.size())
             {
@@ -1152,7 +1150,7 @@ public class WorldAccess {
             if (locator.replacement != null)
             {
                 biome = world.getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(locator.replacement));
-                if (biome == null) return LazyValue.NULL;
+                if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME) ;
             }
             else
             {
diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java
index 589ea740..40f45b17 100644
--- a/src/main/java/carpet/script/exception/Throwables.java
+++ b/src/main/java/carpet/script/exception/Throwables.java
@@ -23,7 +23,7 @@ public class Throwables {
     public static final Throwables UNKNOWN_BIOME         = register("unknown_biome", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_SOUND         = register("unknown_sound", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_PARTICLE      = register("unknown_particle", VALUE_EXCEPTION);
-    public static final Throwables UNKNOWN_POI_TYPE      = register("unknown_poi_type", VALUE_EXCEPTION);
+    public static final Throwables UNKNOWN_POI           = register("unknown_poi", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_DIMENSION     = register("unknown_dimension", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_STRUCTURE     = register("unknown_structure", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_CRITERION     = register("unknown_criterion", VALUE_EXCEPTION);

From 29893ed28ac3cea5c1600c2106597a6796a01532 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 9 Mar 2021 14:43:29 -0400
Subject: [PATCH 37/87] renames

---
 .../script/exception/ExpressionException.java      | 10 +++++-----
 .../carpet/script/exception/ThrowStatement.java    | 14 +++++++-------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index c62e3db7..419fb345 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -16,7 +16,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public final Context context;
     public final Tokenizer.Token token;
     public final List stack = new ArrayList<>();
-    private final Supplier lazyStacktrace;
+    private final Supplier lazyMessage;
     private String cachedMessage = null;
 
     public ExpressionException(Context c, Expression e, String message)
@@ -27,7 +27,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
     {
         super("Error");
-        lazyStacktrace = () -> makeMessage(c, e, t, message);
+        lazyMessage = () -> makeMessage(c, e, t, message);
         token = t;
         context = c;
     }
@@ -35,7 +35,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     {
         super("Error");
         this.stack.addAll(stack);
-        lazyStacktrace = () -> makeMessage(c, e, t, message);
+        lazyMessage = () -> makeMessage(c, e, t, message);
         token = t;
         context = c;
     }
@@ -44,7 +44,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     {
         super("Error");
         this.stack.addAll(stack);
-        lazyStacktrace = () -> makeMessage(c, e, t, messageSupplier.get());
+        lazyMessage = () -> makeMessage(c, e, t, messageSupplier.get());
         token = t;
         context = c;
     }
@@ -89,7 +89,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
     public String getMessage() {
         if (cachedMessage == null)
         {
-        	cachedMessage = lazyStacktrace.get();
+        	cachedMessage = lazyMessage.get();
         }
         return cachedMessage;
     }
diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java
index 74dfa343..f667fdbc 100644
--- a/src/main/java/carpet/script/exception/ThrowStatement.java
+++ b/src/main/java/carpet/script/exception/ThrowStatement.java
@@ -8,8 +8,8 @@ import carpet.script.value.Value;
 
 public class ThrowStatement extends InternalExpressionException
 {
-    private final Throwables thrownExceptionType;
-    private final Value exceptionData;
+    private final Throwables type;
+    private final Value data;
     
     /**
      * Creates a throw exception from a value, and assigns it a specified message.
@@ -20,8 +20,8 @@ public class ThrowStatement extends InternalExpressionException
     public ThrowStatement(Value data, Throwables type)
     {
         super(type.getId());
-        exceptionData = data;
-        thrownExceptionType = type;
+        this.data = data;
+        this.type = type;
     }
 
     /**
@@ -36,12 +36,12 @@ public class ThrowStatement extends InternalExpressionException
     public ThrowStatement(String message, Throwables type)
     {
         super(type.getId());
-        this.exceptionData = StringValue.of(message);
-        this.thrownExceptionType = type;
+        this.data = StringValue.of(message);
+        this.type = type;
     }
     
     @Override
     public ExpressionException promote(Context c, Expression e, Token token) {
-        return new ProcessedThrowStatement(c, e, token, stack, thrownExceptionType, exceptionData);
+        return new ProcessedThrowStatement(c, e, token, stack, type, data);
     }
 }

From 5a366cca9e13d0da70c55a9a6386cfe9d925ff03 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 9 Mar 2021 19:32:51 -0400
Subject: [PATCH 38/87] added subtype option for thrown exceptions

---
 .../language/FunctionsAndControlFlow.md       |  6 +++--
 docs/scarpet/resources/editors/idea/3.txt     |  1 +
 .../scarpet/resources/editors/npp/scarpet.xml |  2 +-
 .../script/exception/ThrowStatement.java      |  7 ++++++
 .../carpet/script/exception/Throwables.java   | 23 ++++++-------------
 .../carpet/script/language/ControlFlow.java   | 10 ++++----
 6 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index 73d5d1fc..a02c2d51 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -295,10 +295,12 @@ Producing:
 {stack: [[, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]}
 ```
 
-### `throw(value?)` `throw(type, value)`
+### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)`
 
-Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw `user_exception` 
+Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception` 
 passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet.
+With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try` 
+statements with custom exceptions.
 
 ### `if(cond, expr, cond?, expr?, ..., default?)`
 
diff --git a/docs/scarpet/resources/editors/idea/3.txt b/docs/scarpet/resources/editors/idea/3.txt
index 333d6259..fc9e16f0 100644
--- a/docs/scarpet/resources/editors/idea/3.txt
+++ b/docs/scarpet/resources/editors/idea/3.txt
@@ -4,6 +4,7 @@ _i
 _x
 _y
 _z
+_trace
 euler
 false
 null
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 5ae29a0f..b44bb643 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -91,7 +91,7 @@
             
             euler pi null true false
             x y z p
-            _ _i _a _x _y _z
+            _ _i _a _x _y _z _trace
             global_
             
                 __command
diff --git a/src/main/java/carpet/script/exception/ThrowStatement.java b/src/main/java/carpet/script/exception/ThrowStatement.java
index f667fdbc..c61470cb 100644
--- a/src/main/java/carpet/script/exception/ThrowStatement.java
+++ b/src/main/java/carpet/script/exception/ThrowStatement.java
@@ -24,6 +24,13 @@ public class ThrowStatement extends InternalExpressionException
         this.type = type;
     }
 
+    public ThrowStatement(Value data, Throwables parent, String subtype)
+    {
+        super(subtype);
+        this.data = data;
+        this.type = new Throwables(subtype, parent);
+    }
+
     /**
      * Creates a throw exception.
* Conveniently creates a value from the {@code value} String diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java index 40f45b17..22857a41 100644 --- a/src/main/java/carpet/script/exception/Throwables.java +++ b/src/main/java/carpet/script/exception/Throwables.java @@ -1,9 +1,7 @@ package carpet.script.exception; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; /** * This class contains default Scarpet catchable types, as well as their inheritance and @@ -14,7 +12,6 @@ public class Throwables { private final Throwables parent; private static final Map byId = new HashMap<>(); - private static final Map> handles = new HashMap<>(); public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null); public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE); @@ -44,14 +41,6 @@ public class Throwables { { Throwables exc = new Throwables(id, parent); byId.put(id, exc); - Set itHandles = new HashSet<>(); - itHandles.add(exc); - while (parent!=null) - { - handles.get(parent.id).add(exc); - parent = parent.parent; - } - handles.put(id, itHandles); return exc; } @@ -61,7 +50,7 @@ public class Throwables { * Use an {@link InternalExpressionException} for that * @param id The exception's value as a {@link String} */ - protected Throwables(String id, Throwables parent) + public Throwables(String id, Throwables parent) { this.id = id; this.parent = parent; @@ -74,7 +63,6 @@ public class Throwables { return properType; } - /** * Checks whether the given filter matches an instance of this exception, by checking equality * with itself and possible parents. @@ -83,9 +71,12 @@ public class Throwables { */ public boolean isRelevantFor(String filter) { - Set relevantSet = handles.get(filter); - if (relevantSet == null) throw new InternalExpressionException("Unknown exception type: "+filter); - return relevantSet.contains(this); + return (id.equals(filter) || (parent != null && parent.isRelevantFor(filter))); + } + + public boolean isUserException() + { + return this == USER_DEFINED || parent == USER_DEFINED; } /** diff --git a/src/main/java/carpet/script/language/ControlFlow.java b/src/main/java/carpet/script/language/ControlFlow.java index 7972d582..e094309e 100644 --- a/src/main/java/carpet/script/language/ControlFlow.java +++ b/src/main/java/carpet/script/language/ControlFlow.java @@ -61,8 +61,10 @@ public class ControlFlow { throw new ThrowStatement(lv.get(0).evalValue(c), Throwables.USER_DEFINED ); case 2: throw new ThrowStatement(lv.get(1).evalValue(c), Throwables.getTypeForException(lv.get(0).evalValue(c).getString())); + case 3: + throw new ThrowStatement(lv.get(2).evalValue(c), Throwables.getTypeForException(lv.get(1).evalValue(c).getString()), lv.get(0).evalValue(c).getString()); default: - throw new InternalExpressionException("throw() can't accept more than 2 parameters"); + throw new InternalExpressionException("throw() can't accept more than 3 parameters"); } }); @@ -79,7 +81,7 @@ public class ControlFlow { { if (lv.size() == 1) { - if (ret.thrownExceptionType != Throwables.USER_DEFINED) + if (!ret.thrownExceptionType.isUserException()) throw ret; return (c_, t_) -> Value.NULL; } @@ -101,7 +103,7 @@ public class ControlFlow { NumericValue.of(f.getToken().linepos+1) )).collect(Collectors.toList())), - StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().collect(Collectors.toMap( + StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().filter(e -> !e.getKey().equals("_trace")).collect(Collectors.toMap( e -> StringValue.of(e.getKey()), e -> e.getValue().evalValue(ret.context) ))), @@ -114,7 +116,7 @@ public class ControlFlow { if (lv.size() == 2) { - if (ret.thrownExceptionType == Throwables.USER_DEFINED) + if (ret.thrownExceptionType.isUserException()) val = lv.get(1).evalValue(c, t); } else From ddd3873427f9b4d075069d3c4ea413f3d2235fcf Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 9 Mar 2021 19:53:54 -0400 Subject: [PATCH 39/87] should stop #726 --- src/main/java/carpet/logging/HUDController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/logging/HUDController.java b/src/main/java/carpet/logging/HUDController.java index 88b3a3ee..a8332203 100644 --- a/src/main/java/carpet/logging/HUDController.java +++ b/src/main/java/carpet/logging/HUDController.java @@ -1,5 +1,6 @@ package carpet.logging; +import carpet.CarpetServer; import carpet.helpers.HopperCounter; import carpet.helpers.TickSpeed; import carpet.logging.logHelpers.PacketCounter; @@ -62,7 +63,7 @@ public class HUDController public static void update_hud(MinecraftServer server) { - if(server.getTicks() % 20 != 0) + if(server.getTicks() % 20 != 0 || CarpetServer.minecraft_server == null) return; player_huds.clear(); From 4dbe6ca41dc0e065aed09c8c863a051d52a3ee01 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Mar 2021 23:30:06 +0000 Subject: [PATCH 40/87] Merge docs for 'Carpet Mod 1.4.29 for Minecraft 1.16.5 and Minecraft 21w10a' --- docs/scarpet/Full.md | 348 +++++++++++++++++++++++++++++++++---------- 1 file changed, 266 insertions(+), 82 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 6e63fd9f..eed59c12 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -296,8 +296,8 @@ For more information, see: [EvalEx GitHub repository](https://github.com/uklimas `scarpet` provides a number of constants that can be used literally in scripts * `null`: nothing, zilch, not even false -* `true`: pure true, or just 1 (one) -* `false`: false truth, or true falsth, 0 (zero) actually +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` * `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 * `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 @@ -343,7 +343,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct 1357-5 => 1352 1357-'5' => 137 3*'foo'-'o' => 'fff' -l(1,3,5)+7 => l(8,10,12) +[1,3,5]+7 => [8,10,12]
As you can see, values can behave differently when mixed with other types in the same expression. @@ -403,8 +403,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea any extra arguments.
-l(1,2,3) ~ 2  => 1
-l(1,2,3) ~ 4  => null
+[1,2,3] ~ 2  => 1
+[1,2,3] ~ 4  => null
 
 'foobar' ~ 'baz'  => null
 'foobar' ~ '.b'  => 'ob'
@@ -430,10 +430,10 @@ Or an example to find if a player has specific enchantment on a held axe (either
 
 
 global_get_enchantment(p, ench) -> (
-$   for(l('main','offhand'),
+$   for(['main','offhand'],
 $      holds = query(p, 'holds', _);
 $      if( holds,
-$         l(what, count, nbt) = holds;
+$         [what, count, nbt] = holds;
 $         if( what ~ '_axe' && nbt ~ ench,
 $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
 $         )
@@ -470,8 +470,8 @@ Examples:
 'foo'*3 => 'foofoofoo'
 'foofoofoo' / 3 => 'foo'
 'foofoofoo'-'o' => 'fff'
-l(1,2,3)+1  => l(2,3,4)
-b = l(100,63,100); b+l(10,0,10)  => l(110,63,110)
+[1,2,3]+1  => [2,3,4]
+b = [100,63,100]; b+[10,0,10]  => [110,63,110]
 {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
 
@@ -513,11 +513,12 @@ second operand is not necessary, it won't be evaluated, which means one can use case of success returns first positive operand (`||`) or last one (`&&`).
-true || false  => 1
-null || false => 0
-null != false || run('kill gnembon')  => 1 // gnembon survives
-null != false && run('kill gnembon')  => 0 // when cheats not allowed
-null != false && run('kill gnembon')  => 1 // gnembon dies, cheats allowed
+true || false  => true
+null || false => false
+false || null => null
+null != false || run('kill gnembon')  // gnembon survives
+null != false && run('kill gnembon')  // when cheats not allowed
+null != false && run('kill gnembon')  // gnembon dies, cheats allowed
 
### `Assignment Operators = <> +=` @@ -531,11 +532,11 @@ require rewriting of the array anyways.
 a = 5  => a == 5
-l(a,b,c) = l(3,4,5) => a==3, b==4, c==5
-l(minx,maxx) = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
-l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f)  => [3,4,5,0,1,2]
-a = l(1,2,3); a += 4  => [1,2,3,4]
-a = l(1,2,3,4); a = filter(a,_!=2)  => [1,3,4]
+[a,b,c] = [3,4,5] => a==3, b==4, c==5
+[minx,maxx] = sort(xi,xj);  // minx assumes min(xi, xj) and maxx, max(xi, xj)
+[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f]  => [3,4,5,0,1,2]
+a = [1,2,3]; a += 4  => [1,2,3,4]
+a = [1,2,3,4]; a = filter(a,_!=2)  => [1,3,4]
 
### `Unary Operators - +` @@ -553,12 +554,12 @@ Require a number, flips the sign. One way to assert it's a number is by crashing flips boolean condition of the expression. Equivalent of `bool(expr)==false`
-!true  => 0
-!false  => 1
-!null  => 1
-!5  => 0
-!l() => 1
-!l(null) => 0
+!true  => false
+!false  => true
+!null  => true
+!5  => false
+!l() => true
+!l(null) => false
 
# Arithmetic operations @@ -696,30 +697,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` -Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is -alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other places where boolean context can be used. This can be used in places where API functions return string values to -represent binary values +represent binary values.
-bool(pi) => 1
-bool(false) => 0
-bool('') => 0
-bool(l()) => 0
-bool(l('')) => 1
-bool('foo') => 1
-bool('false') => 0
-bool('nulL') => 0
-if('false',1,0) => 1
+bool(pi) => true
+bool(false) => false
+bool('') => false
+bool(l()) => false
+bool(l('')) => true
+bool('foo') => true
+bool('false') => false
+bool('nulL') => false
+if('false',1,0) => true
 
### `number(expr)` -Returns a numeric context of the expression. Can be used to read numbers from strings +Returns a numeric context of the expression. Can be used to read numbers from strings, or other types
-number(null) => null
+number(null) => 0
 number(false) => 0
+number(true) => 1
 number('') => null
 number('3.14') => 3.14
 number(l()) => 0
@@ -747,15 +749,15 @@ Supported types (with `"%"` syntax):
 *   `%%`: '%' character
 
 
-str(null) => null
-str(false) => 0
-str('') => null
-str('3.14') => 3.14
-str(l()) => 0
-str(l('')) => 1
-str('foo') => null
-str('3bar') => null
-str(2)+str(2) => 22
+str(null) => 'null'
+str(false) => 'false'
+str('') => ''
+str('3.14') => '3.14'
+str([]) => '[]'
+str(['']) => '[]'
+str('foo') => 'foo'
+str('3bar') => '3bar'
+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'
 
@@ -916,7 +918,7 @@ same random object. Scarpet keeps track of up to 1024 custom random number gener
 map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
-map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
+map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false]
 map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
 
@@ -1217,12 +1219,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound and `continue` have no sense and cannot be used inside of `expr` body.
-all([1,2,3], check_prime(_))  => 1
-all(neighbours(x,y,z), _=='stone')  => 1 // if all neighbours of [x, y, z] are stone
-map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
+all([1,2,3], check_prime(_))  => true
+all(neighbours(x,y,z), _=='stone')  => true // if all neighbours of [x, y, z] are stone
+map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
   => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]]
     // find all 3x3 bedrock structures in the top bedrock layer
-map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_);
+map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_);
         all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8),
    pos(_) )  => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]]
     // ditto, but requiring at most 7 bedrock block in the 18 blocks below them
@@ -1475,15 +1477,73 @@ returns everywhere, but it would often lead to a messy code.
 
 It terminates entire program passing `expr` as the result of the program execution, or null if omitted.
 
-### `try(expr, catch_expr(_)?) ... throw(value?)`
+### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)`
 
-`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 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.
+`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be
+thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form
+catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`, 
+or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first 
+applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only 
+the first matching block will be executed.
+
+Catch expressions are evaluated with 
+`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and 
+column positions), call stack and local
+variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type.
+  
+
+You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching
+exceptions is typically much more expensive comparing to not throwing them.
+
+The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid
+blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods,
+or when an error is outside of the programmers scope, such as problems when reading or decoding files.
+
+This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function:
+- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught, 
+but like everywhere else, doing that sounds like a bad idea.
+  - `value_exception`: This is the parent for any exception that occurs due to an 
+  incorrect argument value provided to a built-in function
+    - `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`, 
+    `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific 
+    errors thrown when a specified internal name does not exist or is invalid.
+  - `io_exception`: This is the parent for any exception that occurs due to an error handling external data.
+    - `nbt_read_error`: Incorrect input NBT file.
+    - `json_read_error`: Incorrect input JSON data.
+  - `user_exception`: Exception thrown by default with `throw` function.
+  
+Synopsis:
+
+inner_call() ->
+(
+   aaa = 'booyah';
+   try(
+      for (range(10), item_tags('stick'+_*'k'));
+   ,
+      print(_trace) // not caught, only catching user_exceptions
+   )
+);
+
+outer_call() -> 
+( 
+   try(
+      inner_call()
+   , 'exception', // catching everything
+      print(_trace)
+   ) 
+);
+
+Producing: +``` +{stack: [[, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]} +``` + +### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)` + +Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception` +passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet. +With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try` +statements with custom exceptions. ### `if(cond, expr, cond?, expr?, ..., default?)` @@ -1518,12 +1578,12 @@ In case to simplify the access with nested objects, you can add chain of address than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
-get(l(range(10)), 5)  => 5
-get(l(range(10)), -1)  => 9
-get(l(range(10)), 10)  => 0
-l(range(10)):93  => 3
+get([range(10)], 5)  => 5
+get([range(10)], -1)  => 9
+get([range(10)], 10)  => 0
+[range(10)]:93  => 3
 get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
-get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar')  => 3
+get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar')  => 3
 
### `has(container, address, ...), has(lvalue)` @@ -1595,13 +1655,13 @@ of the existing elements. Use `replace` to remove and replace existing element. and merges keys from `value` with the compound tag under the path
-a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
-a = l(1, 2, 3); put(a, null, 4); a  => [1, 2, 3, 4]
-a = l(1, 2, 3); put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
-a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a  => [1, 2, 3, 4, 5, 6]
-a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a  => [1, 4, 5, 6, 2, 3]
-a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
-a = m(1,2,3,4); put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
+a = [1, 2, 3]; put(a, 1, 4); a  => [1, 4, 3]
+a = [1, 2, 3]; put(a, null, 4); a  => [1, 2, 3, 4]
+a = [1, 2, 3]; put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
+a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a  => [1, 2, 3, 4, 5, 6]
+a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a  => [1, 4, 5, 6, 2, 3]
+a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+a = {1,2,3,4}; put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
 tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag  => {BlockData:{Properties:[1,2,3,4]}}
 tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag  => {a:[{lvl:1},{lvl:1},{lvl:1}]}
 tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
@@ -1682,10 +1742,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t
 cannot go back nor track where they are in the sequence (see examples).
 
 
-slice([0,1,2,3,4,5], 1, 3)  => [1, 2, 3]
+slice([0,1,2,3,4,5], 1, 3)  => [1, 2]
 slice('foobar', 0, 1)  => 'f'
 slice('foobar', 3)  => 'bar'
-slice(range(10), 3, 5)  => [3, 4, 5]
+slice(range(10), 3, 5)  => [3, 4]
 slice(range(10), 5)  => [5, 6, 7, 8, 9]
 r = range(100); [slice(r, 5, 7), slice(r, 1, 3)]  => [[5, 6], [8, 9]]
 
@@ -1720,7 +1780,7 @@ Primarily to be used in higher order functions
 range(10)  => [...]
-l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+[range(10)]  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 reduce(range(10),_a+_, 0)  => 45
 range(5,10)  => [5, 6, 7, 8, 9]
@@ -2077,6 +2137,8 @@ when it was used. Block values passed in various places like `scan` functions, e
 its properties are needed. This means that if the block at the location changes before its queried in the program this 
 might result in getting the later state, which might not be desired. Consider the following example:
 
+Throws `unknown_block` if provided input is not valid.
+
 
set(10,10,10,'stone');
 scan(10,10,10,0,0,0, b = _);
 set(10,10,10,'air');
@@ -2106,6 +2168,8 @@ properties that the original source block may have contained.
 
 The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
 
+Throws `unknown_block` if provided block to set is not valid
+
 
 set(0,5,0,'bedrock')  => bedrock
 set(l(0,5,0), 'bedrock')  => bedrock
@@ -2184,6 +2248,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l
 Returns true if placement/use was 
 successful, false otherwise.
 
+Throws `unknown_item` if `item` doesn't exist
+
 
 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
@@ -2202,6 +2268,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
 Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial 
 markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
 
+Throws `unknown_poi` if the provided point of interest doesn't exist 
+
 ### `set_biome(pos, biome_name, update=true)`
 
 Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
@@ -2212,6 +2280,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the
 specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
 noise).
 
+Throws `unknown_biome` if the `biome_name` doesn't exist.
+
 ### `update(pos)`
 
 Causes a block update at position.
@@ -2236,6 +2306,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic
 considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the 
 programmer to use that nbt to apply it where it belong
 
+Throws `unknown_item` if `tool` doesn't exist.
+
 Here is a sample code that can be used to mine blocks using items in player inventory, without using player context 
 for mining. Obviously, in this case the use of `harvest` would be much more applicable:
 
@@ -2327,6 +2399,8 @@ back in state definition in various applications where block properties are requ
 
 `block_state` can also accept block names as input, returning block's default state.
 
+Throws `unknown_block` if the provided input is not valid.
+
 
 set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z)  => {waterlogged: false, half: top, open: false, ...}
 set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half')  => top
@@ -2347,6 +2421,8 @@ Without arguments, returns list of available tags, with block supplied (either b
 of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong 
 to this tag, and `true` if the block belongs to the tag.
 
+Throws `unknown_block` if `block` doesn't exist
+
 ### `block_data(pos)`
 
 Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently 
@@ -2381,9 +2457,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
 
 Without arguments, returns the list of biomes in the world.
 
-With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid. 
+With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. 
 
-With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include:
+With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include:
 * `'top_material'`: unlocalized block representing the top surface material
 * `'under_material'`: unlocalized block representing what sits below topsoil
 * `'category'`: the parent biome this biome is derived from. Possible values include:
@@ -2772,6 +2848,7 @@ If structure is not specified, it will return a set of structure names that are
 as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
 should be generated there.
 
+Throws `unknown_structure` if structure doesn't exist.
 
 ### `structures(pos), structures(pos, structure_name)`
 
@@ -2805,6 +2882,8 @@ structure starts.
 Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the 
 structure may depend on the biome, otherwise the default structure for this type will be generated.
 
+Throws `unknown_structure` if structure doesn't exist.
+
 ### `plop(pos, what)`
 
 Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates. 
@@ -2827,7 +2906,9 @@ All generated structures will retain their properties, like mob spawning, howeve
 itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs, 
 because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress.
 
-### `custom_dimension(name, seed?)`
+###  (deprecated) `custom_dimension(name, seed?)`
+
+Deprecated by `create_datapack()` which can be used to setup custom dimensions
 
 Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world
 generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided). 
@@ -3082,7 +3163,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t
 
 ### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')`
 
-String of entity name
+String of entity name or formatted text in the case of `display_name`
 
 
 query(e,'name')  => Leatherworker
@@ -3808,14 +3889,16 @@ an inventory, all API functions 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. 
 
-### item_list(tag?)
+### `item_list(tag?)`
 
 With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid.
 
-### item_tags(item, tag?)
+### `item_tags(item, tag?)`
 
 Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag
 
+Throws `unknown_item` if item doesn't exist.
+
 ### `stack_limit(item)`
 
 Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), 
@@ -3823,6 +3906,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function
 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.
 
+Throws `unknown_item` if item doesn't exist.
+
 
 stack_limit('wooden_axe') => 1
 stack_limit('ender_pearl') => 16
@@ -3833,6 +3918,8 @@ stack_limit('stone') => 64
 
 Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`.
 
+Throws `unknown_item` if item doesn't exist.
+
 
 item_category('wooden_axe') => tools
 item_category('ender_pearl') => misc
@@ -3955,6 +4042,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo
 inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
 
+Throws `unknown_item` if item doesn't exist. + ### `inventory_remove(inventory, item, amount?)` Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing @@ -4357,6 +4446,8 @@ Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'d If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. If the objective was added, returns `true`. If nothing is affected, returns `null` +Throws `unknown_criterion` if criterion doesn't exist. +
 scoreboard_add('counter')
 scoreboard_add('lvl','level')
@@ -4519,6 +4610,8 @@ optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1
 Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient`
 and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a
  corresponding `playsound` command.
+ 
+Throws `unknown_sound` if sound doesn't exist.
 
 ## Particles
 
@@ -4529,12 +4622,15 @@ of 0, and to all players nearby, but these options can be changed via optional a
 command on details on those options. Valid particle names are 
 for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`.
 
+Throws `unknown_particle` if particle doesn't exist.
+
 ### `particle_line(name, pos, pos2, density?, player?)`
 
 Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far 
 apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only
 that player will receive particles.
 
+Throws `unknown_particle` if particle doesn't exist.
 
 ### `particle_box(name, pos, pos2, density?, player?)`
 ### `particle_rect` (deprecated)
@@ -4542,6 +4638,8 @@ that player will receive particles.
 Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is 
 supplied, only that player will receive particles.
 
+Throws `unknown_particle` if particle doesn't exist.
+
 ## Markers
 
 ### `draw_shape(shape, duration, key?, value?, ... )`, 
@@ -4828,6 +4926,10 @@ record to the file. Since files are closed after each write, sending multiple li
 write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
 third argument.
 
+Throws:
+- `nbt_read_error`: When failed to read NBT file.
+- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem.
+
 
 write_file('foo', 'shared_text, ['one', 'two']);
 write_file('foo', 'shared_text', 'three\n', 'four\n');
@@ -4879,6 +4981,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
 
 You can use app data to save non-vanilla information separately from the world and other scripts.
 
+Throws `nbt_read_error` if failed to read app data.
+
 ### `store_app_data(tag)`
 
 Note:  `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
@@ -4893,6 +4997,83 @@ Returns `true` if the file was saved successfully, `false` otherwise.
 
 Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`.
 
+### `create_datapack(name, data)`
+
+Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the 
+json files of the target pack.
+
+Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made.
+Returns `false` if adding of the datapack wasn't successful.
+Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in
+reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` 
+command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`.
+
+Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically.
+
+Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads 
+dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified 
+file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects
+(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more
+experimental `check_hidden_dimensions()` after adding a datapack if needed.
+
+Synopsis:
+
+script run create_datapack('foo', 
+{
+    'foo' -> {
+        'bar.json' -> {
+            'c' -> true,
+            'd' -> false,
+            'e' -> {'foo' -> [1,2,3]},
+            'a' -> 'foobar',
+            'b' -> 5
+        }
+    }
+})
+
+script run create_datapack('funky_world',  {
+    'data' -> {
+        'minecraft' -> {
+            'dimension' -> {
+                'custom_ow.json' -> { 
+                    'type' -> 'minecraft:the_end',
+                    'generator' -> {
+                        'biome_source' -> {
+                             'seed' -> 0,
+                             'large_biomes' -> false,
+                             'type' -> 'minecraft:vanilla_layered'
+                        },
+                        'seed' -> 0,
+                        'settings' -> 'minecraft:nether',
+                        'type' -> 'minecraft:noise'
+                    }
+                }
+            }
+        }
+    }
+});
+check_hidden_dimensions();  => ['funky_world']
+        
+
+ +### `enable_hidden_dimensions()` + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in ` (vanilla client limitation) +but all commands should work just fine with +the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced. +The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings +for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by +vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to +mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang +themselves, so that's about it. + ### `tick_time()` Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. @@ -4956,6 +5137,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt world-localized block, so not `block('stone')`, or a string representing a dimension like: `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc. +Throws `unknown_dimension` if provided dimension can't be found. + ### `view_distance()` Returns the view distance of the server. @@ -5010,6 +5193,7 @@ Available options in the scarpet app space: Relevant world related properties * `world_name` - name of the world * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. From 4e168626dd3ee6ab3c379b026c4b2f1ff3673a94 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 10 Mar 2021 23:15:03 -0400 Subject: [PATCH 41/87] 1.4.29 --- gradle.properties | 2 +- src/main/java/carpet/CarpetSettings.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d74a5b2d..f2e7f327 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G fabric_version=0.29.4+1.16 # Mod Properties - mod_version = 1.4.28 + mod_version = 1.4.29 maven_group = carpet archives_base_name = fabric-carpet diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 1435dc56..eca6a5d3 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT; @SuppressWarnings("CanBeFinal") public class CarpetSettings { - public static final String carpetVersion = "1.4.28+v210224"; + public static final String carpetVersion = "1.4.29+v210310"; public static final Logger LOG = LogManager.getLogger("carpet"); public static boolean skipGenerationChecks = false; public static boolean impendingFillSkipUpdates = false; From e51451db83ee5362dd618644e72494760cfe206f Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 12 Mar 2021 04:02:18 -0400 Subject: [PATCH 42/87] fixed custom worlds loaded via scarpet not ticking properly. --- src/main/java/carpet/script/api/Auxiliary.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 39192581..3c5ce547 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -3,6 +3,7 @@ package carpet.script.api; import carpet.CarpetServer; import carpet.CarpetSettings; import carpet.fakes.MinecraftServerInterface; +import carpet.fakes.ServerWorldInterface; import carpet.fakes.StatTypeInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; @@ -1209,7 +1210,7 @@ public class Auxiliary { RegistryKey registryKey2 = RegistryKey.of(Registry.DIMENSION, registryKey.getValue()); DimensionType dimensionType3 = entry.getValue().getDimensionType(); ChunkGenerator chunkGenerator3 = entry.getValue().getChunkGenerator(); - UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, saveProperties.getMainWorldProperties()); + UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, ((ServerWorldInterface) server.getOverworld()).getWorldPropertiesCM()); ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false); server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder())); existing_worlds.put(registryKey2, serverWorld2); From ece43b6213ff8c82b21f8af2921f2c50b0f73e7a Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 12 Mar 2021 06:11:07 -0400 Subject: [PATCH 43/87] functions returning integer result keep integer precision --- src/main/java/carpet/script/language/Arithmetic.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/script/language/Arithmetic.java b/src/main/java/carpet/script/language/Arithmetic.java index cb8d5281..c5e64034 100644 --- a/src/main/java/carpet/script/language/Arithmetic.java +++ b/src/main/java/carpet/script/language/Arithmetic.java @@ -60,9 +60,9 @@ public class Arithmetic { expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); expression.addMathematicalUnaryFunction("sqrt", Math::sqrt); expression.addMathematicalUnaryFunction("abs", Math::abs); - expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); - expression.addMathematicalUnaryFunction("floor", Math::floor); - expression.addMathematicalUnaryFunction("ceil", Math::ceil); + expression.addMathematicalUnaryIntFunction("round", Math::round); + expression.addMathematicalUnaryIntFunction("floor", n -> (long)Math.floor(n)); + expression.addMathematicalUnaryIntFunction("ceil", n -> (long)Math.ceil(n)); expression.addLazyFunction("mandelbrot", 3, (c, t, lv) -> { double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); From eab24f7896ff76782788852c8ed70fa2adb518f3 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 12 Mar 2021 06:11:55 -0400 Subject: [PATCH 44/87] added reset_seed --- docs/scarpet/language/SystemFunctions.md | 5 +++++ docs/scarpet/resources/editors/idea/1.txt | 1 + docs/scarpet/resources/editors/npp/scarpet.xml | 4 ++-- src/main/java/carpet/script/Expression.java | 5 +++++ src/main/java/carpet/script/ScriptHost.java | 4 ++++ src/main/java/carpet/script/language/Sys.java | 4 ++++ 6 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index bdc17d72..eb453bcd 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -239,6 +239,11 @@ map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
+## `reset_seed(seed)` + +Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value +indicating if the given seed has been used or not. + ### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)` returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples diff --git a/docs/scarpet/resources/editors/idea/1.txt b/docs/scarpet/resources/editors/idea/1.txt index d69ced42..e83e03da 100644 --- a/docs/scarpet/resources/editors/idea/1.txt +++ b/docs/scarpet/resources/editors/idea/1.txt @@ -63,6 +63,7 @@ reduce relu replace replace_first +reset_seed return round sec diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index b44bb643..c274f9bd 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -25,7 +25,7 @@ - not fact rand + not fact rand reset_seed sin cos tan asin acos atan atan2 sinh cosh tanh sec csc sech csch cot acot coth asinh acosh atanh rad deg log log10 log1p @@ -46,7 +46,7 @@ var undef vars if loop map filter first all c_for for while reduce continue break - import call outer type hash_code bool number str length rand sleep time profile_expr lower upper title replace replace_first + import call outer type hash_code bool number str length sleep time profile_expr lower upper title replace replace_first block pos pos_offset solid air liquid flammable diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index cb95e860..7aa7ec0f 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -313,6 +313,11 @@ public class Expression addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); } + public void addMathematicalUnaryIntFunction(String name, Function fun) + { + addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); + } + public void addMathematicalBinaryFunction(String name, BiFunction fun) { addBinaryFunction(name, (w, v) -> diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 1cae3c97..255f76d4 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -42,6 +42,10 @@ public abstract class ScriptHost return randomizers.computeIfAbsent(aLong, Random::new); } + public boolean resetRandom(long aLong) { + return randomizers.remove(aLong) != null; + } + public static class ModuleData { Module parent; diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java index 4655f9d6..dc2a75df 100644 --- a/src/main/java/carpet/script/language/Sys.java +++ b/src/main/java/carpet/script/language/Sys.java @@ -210,6 +210,10 @@ public class Sys { Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); return (cc, tt) -> retval; }); + expression.addLazyFunction("reset_seed", 1, (c, t, lv) -> { + boolean gotIt = c.host.resetRandom(NumericValue.asNumber(lv.get(0).evalValue(c)).getLong()); + return gotIt?LazyValue.TRUE:LazyValue.FALSE; + }); expression.addLazyFunction("perlin", -1, (c, t, lv) -> { From 36f766ae752d50bd7f5727c421f316ba401d4de3 Mon Sep 17 00:00:00 2001 From: Ghoulboy Date: Fri, 12 Mar 2021 22:36:25 +0100 Subject: [PATCH 45/87] Removing return statements to improve performance by a few microprocessor ticks to satisfy @altrisi --- .../resources/assets/carpet/scripts/shapes.scl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/shapes.scl b/src/main/resources/assets/carpet/scripts/shapes.scl index 6da87888..adad5bcb 100644 --- a/src/main/resources/assets/carpet/scripts/shapes.scl +++ b/src/main/resources/assets/carpet/scripts/shapes.scl @@ -25,7 +25,7 @@ draw_sphere(args)->( ) ) ); - return(positions) + positions ); draw_diamond(args)->( @@ -41,7 +41,7 @@ draw_diamond(args)->( positions += [pos:0+x, pos:1 -y, pos:2-z]; ) ); - return(positions) + positions ); draw_filled_diamond(args)->( @@ -50,7 +50,7 @@ draw_filled_diamond(args)->( for(diamond(pos,radius,radius), positions += pos(_) ); - return(positions) + positions ); draw_pyramid(args)->( @@ -62,7 +62,7 @@ draw_pyramid(args)->( r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height); positions += fill_flat(pos, _, r, is_square, orientation, !((pointup&&_==0)||(!pointup && _==height-1)) && hollow)//Always close bottom off ); - return(positions) + positions ); draw_prism(args)->( @@ -94,7 +94,7 @@ draw_prism(args)->( ) ); ) - return(positions) + positions ); fill_flat(pos, offset, dr, rectangle, orientation, hollow)-> @@ -122,7 +122,7 @@ fill_flat_circle(pos, offset, dr, orientation, hollow) ->( //Bresenham circle al a+=1; positions += fill_flat_circle_points(pos, orientation, offset, hollow, a, b) ); - return(positions) + positions ); fill_flat_circle_points(pos, orientation, offset, hollow, a, b) ->( @@ -229,5 +229,5 @@ fill_flat_rectangle(pos, offset, dr, orientation, hollow) -> ( ) ), ); - return(positions) + positions ); From a8bae66b2a99e14015ed15c5883e8dac1501be01 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 14 Mar 2021 01:57:45 -0400 Subject: [PATCH 46/87] allow to modify built-in temporary variables, otherwise its tough to reduce to a list. Let's see what happens. --- src/main/java/carpet/script/value/Value.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 573ed02c..389ede83 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -141,12 +141,12 @@ public abstract class Value implements Comparable, Cloneable public void assertAssignable() { - if (boundVariable == null || boundVariable.startsWith("_")) + if (boundVariable == null)// || boundVariable.startsWith("_")) { - if (boundVariable != null) + /*if (boundVariable != null) { throw new InternalExpressionException(boundVariable+ " cannot be assigned a new value"); - } + }*/ throw new InternalExpressionException(getString()+ " is not a variable"); } From e81f3d9629af8e3013703488fe417239e9259a7d Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 14 Mar 2021 01:59:04 -0400 Subject: [PATCH 47/87] sound and particle with now arguments now return list of available options. --- docs/scarpet/api/Auxiliary.md | 10 ++++++++-- docs/scarpet/api/BlocksAndWorldAccess.md | 4 ++-- src/main/java/carpet/script/api/Auxiliary.java | 11 +++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 1a7d3c33..78f61be4 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -4,7 +4,7 @@ Collection of other methods that control smaller, yet still important aspects of ## Sounds -### `sound(name, pos, volume?, pitch?, mixer?)` +### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)` Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. @@ -12,17 +12,23 @@ Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostil and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. +Used with no arguments, return the list of available sound names. + Throws `unknown_sound` if sound doesn't exist. ## Particles -### `particle(name, pos, count?. spread?, speed?, player?)` +### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)` Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed` of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle` command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. +Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid +particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration +to be valid, like `dust`, `block` etc. Should be used as a reference only. + Throws `unknown_particle` if particle doesn't exist. ### `particle_line(name, pos, pos2, density?, player?)` diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 3f27bb4d..bcde5013 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -315,13 +315,13 @@ 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, ... }'
 
-### `poi(pos), poi(pos, radius?, type?, status?, column?)` +### `poi(pos), poi(pos, radius?, type?, status?, column_search?)` Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`. If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status. -With `column` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire +With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius` radius. diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 3c5ce547..a1aaec2f 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -163,6 +163,11 @@ public class Auxiliary { { expression.addLazyFunction("sound", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; + if (lv.size() == 0) + { + Value ret = ListValue.wrap(Registry.SOUND_EVENT.getIds().stream().map(ValueConversions::of)); + return (_c, _t) -> ret; + } String rawString = lv.get(0).evalValue(c).getString(); Identifier soundName = new Identifier(rawString); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); @@ -200,6 +205,12 @@ public class Auxiliary { expression.addLazyFunction("particle", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; + if (lv.size() == 0) + { + Value ret = ListValue.wrap(Registry.PARTICLE_TYPE.getIds().stream().map(ValueConversions::of)); + return (_c, _t) -> ret; + } + MinecraftServer ms = cc.s.getMinecraftServer(); ServerWorld world = cc.s.getWorld(); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); From 15c5c8753f6c0be6285ac9733f95ac5bff5f3dfa Mon Sep 17 00:00:00 2001 From: replaceitem <40722305+replaceitem@users.noreply.github.com> Date: Sun, 14 Mar 2021 07:12:29 +0100 Subject: [PATCH 48/87] Optimizing scoreboard API with scoreboard_property (#753) * Adding scoreboard_property, deprecating scoreboard_remove * Docs * Editor keywords * Whoops * adding scoreboard_remove back * Making rendertype snake case * Double scoreboard_remove * Removed deprecation --- docs/scarpet/api/Scoreboard.md | 14 ++- docs/scarpet/resources/editors/idea/2.txt | 1 + .../scarpet/resources/editors/npp/scarpet.xml | 2 +- .../java/carpet/script/api/Scoreboards.java | 96 +++++++++++++++++-- 4 files changed, 101 insertions(+), 12 deletions(-) diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index 9d152098..881df496 100644 --- a/docs/scarpet/api/Scoreboard.md +++ b/docs/scarpet/api/Scoreboard.md @@ -6,13 +6,12 @@ Displays or modifies individual scoreboard values. With no arguments, returns th With specified `objective`, lists all keys (players) associated with current objective, or `null` if objective does not exist. With specified `objective` and `key`, returns current value of the objective for a given player (key). With additional `value` sets a new scoreboard - value, returning previous value associated with the `key`. + value, returning previous value associated with the `key`. If the `value` is null, resets the scoreboard value. ### `scoreboard_add(objective, criterion?)` Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. -If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. -If the objective was added, returns `true`. If nothing is affected, returns `null` +Returns `true` if the objective was created, or `null` if an objective with the specified name already exists. Throws `unknown_criterion` if criterion doesn't exist. @@ -33,6 +32,15 @@ for the objective. Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, returns `null`. +### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)` + +Reads a property of an `objective` or sets it to a `value` if specified. Available properties are: + +* `criterion` +* `display_name` (Formatted text supported) +* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot +* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified + # Team ### `team_list()`, `team_list(team)` diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 1c3251b3..42a7bf9e 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -97,6 +97,7 @@ scoreboard scoreboard_add scoreboard_remove scoreboard_display +scoreboard_property tag_matches team_add team_leave diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index c274f9bd..b10e15e4 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -62,7 +62,7 @@ display_title - scoreboard scoreboard_add scoreboard_remove scoreboard_display + scoreboard scoreboard_add scoreboard_remove scoreboard_display scoreboard_property team_add team_list team_remove team_leave team_property diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index f0a2e598..f60ca14a 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -35,6 +35,8 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; public class Scoreboards { @@ -79,16 +81,27 @@ public class Scoreboards { Value ret = ListValue.wrap(scoreboard.getAllPlayerScores(objective).stream().map(s -> new StringValue(s.getPlayerName())).collect(Collectors.toList())); return (_c, _t) -> ret; } + String key = getScoreboardKeyFromValue(lv.get(1).evalValue(c)); - if (!scoreboard.playerHasObjective(key, objective) && lv.size()==2) + if(lv.size()==2) { + if(scoreboard.playerHasObjective(key, objective)) { + return (_c,_t) -> NumericValue.of(scoreboard.getPlayerScore(key,objective).getScore()); + } return LazyValue.NULL; - ScoreboardPlayerScore scoreboardPlayerScore = scoreboard.getPlayerScore(key, objective); - Value retval = new NumericValue(scoreboardPlayerScore.getScore()); - if (lv.size() > 2) - { - scoreboardPlayerScore.setScore(NumericValue.asNumber(lv.get(2).evalValue(c)).getInt()); } - return (_c, _t) -> retval; + + Value value = lv.get(2).evalValue(c); + if(value.isNull()) { + ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective); + scoreboard.resetPlayerScore(key,objective); + return (_c,_t) -> NumericValue.of(score.getScore()); + } + if (value instanceof NumericValue) { + ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective); + score.setScore(NumericValue.asNumber(value).getInt()); + return (_c,_t) -> NumericValue.of(score.getScore()); + } + throw new InternalExpressionException("'scoreboard' requires a number or null as the third parameter"); }); expression.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> @@ -139,6 +152,7 @@ public class Scoreboards { ScoreboardObjective objective = scoreboard.getObjective(objectiveName); if (objective != null) { + c.host.issueDeprecation("reading or modifying an objective's criterion with scoreboard_add"); if(lv.size() == 1) return (_c, _t) -> StringValue.of(objective.getCriterion().getName()); if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.NULL; ((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective); @@ -152,6 +166,72 @@ public class Scoreboards { return LazyValue.TRUE; }); + expression.addLazyFunction("scoreboard_property", -1, (c, t, lv) -> + { + if(lv.size() < 2) throw new InternalExpressionException("'scoreboard_property' requires at least two parameters"); + CarpetContext cc = (CarpetContext)c; + Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + ScoreboardObjective objective = scoreboard.getObjective(lv.get(0).evalValue(c).getString()); + if(objective == null) return LazyValue.NULL; + + boolean modify = lv.size() > 2; + Value setValue = null; + if(modify) { + setValue = lv.get(2).evalValue(c); + } + String property = lv.get(1).evalValue(c).getString(); + switch (property) { + case "criterion": + if(modify) { + ScoreboardCriterion criterion = ScoreboardCriterion.createStatCriterion(setValue.getString()).orElse(null); + if (criterion==null) throw new InternalExpressionException("Unknown scoreboard criterion: "+ setValue.getString()); + if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.FALSE; + ((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective); + ((ScoreboardObjective_scarpetMixin) objective).setCriterion(criterion); + (((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().computeIfAbsent(criterion, (criterion1) -> Lists.newArrayList())).add(objective); + scoreboard.updateObjective(objective); + return LazyValue.TRUE; + } + return (_c, _t) -> StringValue.of(objective.getCriterion().getName()); + case "display_name": + if(modify) { + Text text = (setValue instanceof FormattedTextValue)?((FormattedTextValue) setValue).getText():new LiteralText(setValue.getString()); + objective.setDisplayName(text); + return LazyValue.TRUE; + } + return (_c, _t) -> new FormattedTextValue(objective.getDisplayName()); + case "display_slot": + if(modify) { + int slotId = Scoreboard.getDisplaySlotId(setValue.getString()); + if(slotId == -1) throw new InternalExpressionException("Unknown scoreboard display slot: " + setValue.getString()); + if(objective.equals(scoreboard.getObjectiveForSlot(slotId))) { + return LazyValue.FALSE; + } + scoreboard.setObjectiveSlot(slotId,objective); + return LazyValue.TRUE; + } + + List slots = new ArrayList<>(); + for(int i = 0; i < 19; i++) { + if (scoreboard.getObjectiveForSlot(i) == objective) { + String slotName = Scoreboard.getDisplaySlotName(i); + slots.add(StringValue.of(slotName)); + } + } + return (_c, _t) -> ListValue.wrap(slots); + case "render_type": + if(modify) { + ScoreboardCriterion.RenderType renderType = ScoreboardCriterion.RenderType.getType(setValue.getString().toLowerCase()); + if(objective.getRenderType().equals(renderType)) return LazyValue.FALSE; + objective.setRenderType(renderType); + return LazyValue.TRUE; + } + return (_c, _t) -> StringValue.of(objective.getRenderType().getName()); + default: + throw new InternalExpressionException("scoreboard property '" + property + "' is not a valid property"); + } + }); + expression.addLazyFunction("scoreboard_display", 2, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; @@ -410,7 +490,7 @@ public class Scoreboards { team.setSuffix(suffix); break; default: - throw new InternalExpressionException("team property " + propertyVal.getString() + " is not a valid property"); + throw new InternalExpressionException("team property '" + propertyVal.getString() + "' is not a valid property"); } return LazyValue.TRUE; }); From a3acaed46611ac3d5b6beca7e89e310650eb27a2 Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 15 Mar 2021 20:38:06 +0100 Subject: [PATCH 49/87] Make command stacktrace mixin not required --- src/main/java/carpet/mixins/CommandManagerMixin.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/mixins/CommandManagerMixin.java b/src/main/java/carpet/mixins/CommandManagerMixin.java index c2677cbf..eb96d12b 100644 --- a/src/main/java/carpet/mixins/CommandManagerMixin.java +++ b/src/main/java/carpet/mixins/CommandManagerMixin.java @@ -44,9 +44,11 @@ public abstract class CommandManagerMixin @SuppressWarnings("UnresolvedMixinReference") @Redirect(method = "execute", at = @At( - value = "INVOKE", - target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z" - )) + value = "INVOKE", + target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z" + ), + require = 0 + ) private boolean doesOutputCommandStackTrace(Logger logger) { if (CarpetSettings.superSecretSetting) From 57f34a9eac7f866defc13f0b32c92010956a63e6 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 17 Mar 2021 08:23:56 -0300 Subject: [PATCH 50/87] deprecated some IO functions --- .../carpet/script/argument/FileArgument.java | 345 ++++++++++++++++++ .../carpet/script/bundled/FileModule.java | 8 +- .../java/carpet/script/bundled/Module.java | 13 +- 3 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 src/main/java/carpet/script/argument/FileArgument.java diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java new file mode 100644 index 00000000..f3ad42fe --- /dev/null +++ b/src/main/java/carpet/script/argument/FileArgument.java @@ -0,0 +1,345 @@ +package carpet.script.argument; + +import carpet.CarpetServer; +import carpet.script.Context; +import carpet.script.LazyValue; +import carpet.script.bundled.Module; +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.Value; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.PositionTracker; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagReaders; +import net.minecraft.util.WorldSavePath; +import org.apache.commons.io.FilenameUtils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class FileArgument +{ + public String resource; + public Type type; + public String zipContainer; + public boolean isFolder; + public boolean isShared; + public Reason reason; + + public final static Object writeIOSync = new Object(); + + public enum Type + { + RAW("raw", ".txt"), + TEXT("text", ".txt"), + NBT("nbt", ".nbt"), + JSON("json", ".json"), + FOLDER("folder", ""); + + private final String id; + private final String extension; + + public static Map of = Arrays.stream(values()).collect(Collectors.toMap(t -> t.id, t -> t)); + + Type(String id, String extension) + { + this.id = id; + this.extension = extension; + } + } + + public enum Reason + { + READ, CREATE, DELETE + } + + private FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason) + { + this.resource = resource; + this.type = type; + this.zipContainer = zipContainer; + this.isFolder = isFolder; + this.isShared = isShared; + this.reason = reason; + } + + public static FileArgument from(List lv, Context c, boolean isFolder, Reason reason) + { + if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments"); + String resource = recognizeResource(lv.get(0).evalValue(c), isFolder); + String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); + boolean shared = origtype.startsWith("shared_"); + String typeString = shared ? origtype.substring(7) : origtype; //len(shared_) + Type type = Type.of.get(typeString); + if (!Type.of.containsKey(typeString)) + throw new InternalExpressionException("Unsupported file type: "+origtype); + + if (type==Type.FOLDER && !isFolder) + throw new InternalExpressionException("Folder types are no supported for this IO function"); + return new FileArgument(resource, type,null, isFolder, shared, reason); + + } + + public static String recognizeResource(Value value, boolean isFolder) + { + String origfile = value.getString(); + String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", ""); + file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); + if (file.isEmpty() && !isFolder) + { + throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); + } + return file; + } + + private Path toPath(Module module) + { + if (!isShared && (module == null || module.getName() == null)) return null; + return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resource)+type.extension); + } + + private Path moduleRootPath(Module module) + { + if (!isShared && (module == null || module.getName() == null)) return null; + return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, "")); + } + + private String getDescriptor(Module module, String res) + { + if (isShared) + { + return res.isEmpty()?"shared":"shared/"+res; + } + if (module != null && module.getName() != null) // appdata + { + return module.getName()+".data"+(res==null || res.isEmpty()?"":"/"+res); + } + throw new RuntimeException("Invalid file descriptor: "+res); + } + + public Stream listFiles(Module module) + { + Path dir = toPath(module); + if (dir == null || !Files.exists(dir)) return null; + String ext = type.extension; + try + { + return Files.list(dir). + filter(path -> isFolder?Files.isDirectory(path):path.toString().endsWith(ext)); + } + catch (IOException ignored) + { + return null; + } + } + + public Stream listFolder(Module module) + { + Stream result; + synchronized (writeIOSync) { result = listFiles(module); } + if (result == null) return null; + Path rootPath = moduleRootPath(module); + Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/")); + if (type != Type.FOLDER) + return strings.map(FilenameUtils::removeExtension); + return strings; + } + + public boolean appendToTextFile(Module module, List message) + { + Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared); + if (dataFile == null) return false; + if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; + synchronized (writeIOSync) + { + try + { + OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) + { + for (String line: message) + { + writer.append(line); + if (type == Type.TEXT) writer.newLine(); + } + } + } + catch (IOException e) + { + return false; + } + return true; + } + } + + public Tag getNbtData(Module module) // aka getData + { + Path dataFile = toPath(module); + if (dataFile == null) return null; + if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null; + synchronized (writeIOSync) { return readTag(dataFile.toFile()); } + } + + //copied private method from net.minecraft.nbt.NbtIo.read() + // to read non-compound tags - these won't be compressed + public static Tag readTag(File file) + { + try + { + return NbtIo.readCompressed(file); + } + catch (IOException e) + { + // Copy of NbtIo.read(File) because that's now client-side only + if (!file.exists()) + { + return null; + } + try (DataInputStream in = new DataInputStream(new FileInputStream(file))) + { + return NbtIo.read(in); + } + catch (IOException ioException) + { + try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file))) + { + byte byte_1 = dataInput_1.readByte(); + if (byte_1 == 0) + { + return null; + } + else + { + dataInput_1.readUTF(); + return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT); + } + } + catch (IOException ignored) + { + } + return null; + } + } + } + + public boolean saveNbtData(Module module, Tag tag) // aka saveData + { + Path dataFile = toPath(module); + if (dataFile == null) return false; + if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; + synchronized (writeIOSync) { return writeTag(tag, dataFile.toFile()); } + } + + //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write + public static boolean writeTag(Tag tag_1, File file) + { + File file_2 = new File(file.getAbsolutePath() + "_tmp"); + if (file_2.exists()) file_2.delete(); + + if (tag_1 instanceof CompoundTag) + { + try + { + NbtIo.writeCompressed((CompoundTag) tag_1, file_2); + } + catch (IOException e) + { + return false; + } + } + else + { + try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2))) + { + dataOutputStream_1.writeByte(tag_1.getType()); + if (tag_1.getType() != 0) + { + dataOutputStream_1.writeUTF(""); + tag_1.write(dataOutputStream_1); + } + } + catch (IOException e) + { + return false; + } + } + if (file.exists()) file.delete(); + if (!file.exists()) file_2.renameTo(file); + return true; + } + + public boolean dropExistingFile(Module module) + { + Path dataFile = toPath(module); + if (dataFile == null) return false; + synchronized (writeIOSync) { return dataFile.toFile().delete(); } + } + + public List listFile(Module module) + { + Path dataFile = toPath(module); + if (dataFile == null) return null; + if (!dataFile.toFile().exists()) return null; + synchronized (writeIOSync) { return listFileContent(dataFile); } + } + + public static List listFileContent(Path filePath) + { + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + List result = new ArrayList<>(); + for (;;) { + String line = reader.readLine(); + if (line == null) + break; + result.add(line.replaceAll("[\n\r]+","")); + } + return result; + } + catch (IOException e) + { + return null; + } + } + + public JsonElement readJsonFile(Module module) { + Path dataFile = toPath(module); + if (dataFile == null) return null; + if (!dataFile.toFile().exists()) return null; + synchronized (writeIOSync) { return readJsonContent(dataFile); } + } + + public static JsonElement readJsonContent(Path filePath) + { + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) + { + return new JsonParser().parse(new JsonReader(reader)); + } + catch (IOException e) + { + return null; + } + } + +} diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index 4684b0fa..20cd65af 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -68,6 +68,7 @@ public class FileModule extends Module //copied private method from net.minecraft.nbt.NbtIo.read() // to read non-compound tags - these won't be compressed + @Deprecated public static Tag read(File file) { try @@ -109,6 +110,7 @@ public class FileModule extends Module } //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write + @Deprecated public static boolean write(Tag tag_1, File file) { File file_2 = new File(file.getAbsolutePath() + "_tmp"); @@ -146,6 +148,7 @@ public class FileModule extends Module return true; } + @Deprecated public static boolean appendText(Path filePath, boolean addNewLines, List data) { try @@ -168,6 +171,7 @@ public class FileModule extends Module } + @Deprecated public static List listFileContent(Path filePath) { try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { @@ -185,7 +189,8 @@ public class FileModule extends Module return null; } } - + + @Deprecated public static JsonElement readJsonContent(Path filePath) { try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) @@ -198,6 +203,7 @@ public class FileModule extends Module } } + @Deprecated public static Stream listFiles(Path dir, String ext) { boolean folderListing = ext.equals("folder"); diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java index 4a352e8e..ea6231b5 100644 --- a/src/main/java/carpet/script/bundled/Module.java +++ b/src/main/java/carpet/script/bundled/Module.java @@ -19,7 +19,9 @@ public abstract class Module public abstract String getName(); public abstract String getCode(); public abstract boolean isLibrary(); + @Deprecated public final static Object writeIOSync = new Object(); + @Deprecated public final static Map supportedTypes = ImmutableMap.of( "raw", ".txt", "text", ".txt", @@ -28,6 +30,7 @@ public abstract class Module "folder", "folder" ); + @Deprecated private static String getDescriptor(Module module, String file, boolean isShared) { if (isShared) @@ -41,6 +44,7 @@ public abstract class Module throw new RuntimeException("Invalid file descriptor: "+file); } + @Deprecated public static Tag getData(Module module, String file, boolean isShared) { Path dataFile = resolveResource(module, file, ".nbt", isShared); @@ -49,6 +53,7 @@ public abstract class Module synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); } } + @Deprecated public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) { Path dataFile = resolveResource(module, file, ".nbt", isShared); @@ -57,6 +62,7 @@ public abstract class Module synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); } } + @Deprecated public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message) { Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); @@ -65,6 +71,7 @@ public abstract class Module synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); } } + @Deprecated public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared) { Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); @@ -72,6 +79,7 @@ public abstract class Module synchronized (writeIOSync) { return dataFile.toFile().delete(); } } + @Deprecated public static List listFile(Module module, String resourceName, String type, boolean isShared) { Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); @@ -79,7 +87,8 @@ public abstract class Module if (!dataFile.toFile().exists()) return null; synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } } - + + @Deprecated public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) { Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared); if (dataFile == null) return null; @@ -87,6 +96,7 @@ public abstract class Module synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); } } + @Deprecated public static Stream listFolder(Module module, String resourceName, String type, boolean isShared) { Path dir = resolveResource(module, resourceName, null, isShared); @@ -103,6 +113,7 @@ public abstract class Module return strings; } + @Deprecated private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared) { if (!isShared && (module == null || module.getName() == null)) return null; From 31560947195bf55f5d6926229f7f6d0195f38c30 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 17 Mar 2021 19:00:11 -0300 Subject: [PATCH 51/87] 1.4.30 --- gradle.properties | 2 +- src/main/java/carpet/CarpetSettings.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index f2e7f327..de926908 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G fabric_version=0.29.4+1.16 # Mod Properties - mod_version = 1.4.29 + mod_version = 1.4.30 maven_group = carpet archives_base_name = fabric-carpet diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index eca6a5d3..b3ef771c 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT; @SuppressWarnings("CanBeFinal") public class CarpetSettings { - public static final String carpetVersion = "1.4.29+v210310"; + public static final String carpetVersion = "1.4.30+v210317"; public static final Logger LOG = LogManager.getLogger("carpet"); public static boolean skipGenerationChecks = false; public static boolean impendingFillSkipUpdates = false; From 544cf53fbf9b3500e322a6537cc1a880fde0d41f Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 18 Mar 2021 00:39:54 -0300 Subject: [PATCH 52/87] catching error when server is closing. --- src/main/java/carpet/settings/SettingsManager.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/settings/SettingsManager.java b/src/main/java/carpet/settings/SettingsManager.java index 489262d4..3c0ae01a 100644 --- a/src/main/java/carpet/settings/SettingsManager.java +++ b/src/main/java/carpet/settings/SettingsManager.java @@ -318,10 +318,12 @@ public class SettingsManager } server.send(new ServerTask(this.server.getTicks(), () -> { - for (ServerPlayerEntity entityplayermp : server.getPlayerManager().getPlayerList()) - { - server.getCommandManager().sendCommandTree(entityplayermp); + try { + for (ServerPlayerEntity entityplayermp : server.getPlayerManager().getPlayerList()) { + server.getCommandManager().sendCommandTree(entityplayermp); + } } + catch (NullPointerException ignored) {} })); } From 4fdee473f0ba635fff0674cc0a6224f6b3008bcc Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 18 Mar 2021 00:49:21 -0300 Subject: [PATCH 53/87] added lean and turn to labels --- docs/scarpet/api/Auxiliary.md | 2 +- src/main/java/carpet/script/utils/ShapeDispatcher.java | 8 ++++++++ src/main/java/carpet/script/utils/ShapesRenderer.java | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 78f61be4..3a0bbc30 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -114,7 +114,7 @@ Available shapes: * `align` - text alignment with regards to `pos`. Default is `center` (displayed text is centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos` indicates the end of text). - * `tilt` - additional rotation of the text on the canvas + * `tilt`, `lean`, `turn` - additional rotations of the text on the canvas along all three axis * `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`) with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which can be used to display multiple lines of text bound to the same `pos` diff --git a/src/main/java/carpet/script/utils/ShapeDispatcher.java b/src/main/java/carpet/script/utils/ShapeDispatcher.java index 87a153f2..404b2753 100644 --- a/src/main/java/carpet/script/utils/ShapeDispatcher.java +++ b/src/main/java/carpet/script/utils/ShapeDispatcher.java @@ -418,6 +418,8 @@ public class ShapeDispatcher put("facing", new StringValue("player")). put("raise", new NumericValue(0)). put("tilt", new NumericValue(0)). + put("lean", new NumericValue(0)). + put("turn", new NumericValue(0)). put("indent", new NumericValue(0)). put("height", new NumericValue(0)). put("align", new StringValue("center")). @@ -439,6 +441,8 @@ public class ShapeDispatcher Direction facing; float raise; float tilt; + float lean; + float turn; float size; float indent; int align; @@ -487,6 +491,8 @@ public class ShapeDispatcher raise = NumericValue.asNumber(options.getOrDefault("raise", optional.get("raise"))).getFloat(); tilt = NumericValue.asNumber(options.getOrDefault("tilt", optional.get("tilt"))).getFloat(); + lean = NumericValue.asNumber(options.getOrDefault("lean", optional.get("lean"))).getFloat(); + turn = NumericValue.asNumber(options.getOrDefault("turn", optional.get("turn"))).getFloat(); indent = NumericValue.asNumber(options.getOrDefault("indent", optional.get("indent"))).getFloat(); height = NumericValue.asNumber(options.getOrDefault("height", optional.get("height"))).getFloat(); @@ -519,6 +525,8 @@ public class ShapeDispatcher if (facing!= null) hash ^= facing.hashCode(); hash *= 1099511628211L; hash ^= Float.hashCode(raise); hash *= 1099511628211L; hash ^= Float.hashCode(tilt); hash *= 1099511628211L; + hash ^= Float.hashCode(lean); hash *= 1099511628211L; + hash ^= Float.hashCode(turn); hash *= 1099511628211L; hash ^= Float.hashCode(indent); hash *= 1099511628211L; hash ^= Float.hashCode(height); hash *= 1099511628211L; hash ^= Float.hashCode(size); hash *= 1099511628211L; diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java index 8be35a77..436d8632 100644 --- a/src/main/java/carpet/script/utils/ShapesRenderer.java +++ b/src/main/java/carpet/script/utils/ShapesRenderer.java @@ -278,6 +278,14 @@ public class ShapesRenderer { RenderSystem.rotatef(shape.tilt, 0.0f, 0.0f, 1.0f); } + if (shape.lean!=0.0f) + { + RenderSystem.rotatef(shape.lean, 1.0f, 0.0f, 0.0f); + } + if (shape.turn!=0.0f) + { + RenderSystem.rotatef(shape.turn, 0.0f, 1.0f, 0.0f); + } RenderSystem.translatef(-10*shape.indent, -10*shape.height-9, (float) (-10*renderEpsilon)-10*shape.raise); //if (visibleThroughWalls) RenderSystem.disableDepthTest(); RenderSystem.scalef(-1.0F, 1.0F, 1.0F); From 087312abfaffc41563db517a00c4e98405d6efa8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 18 Mar 2021 00:58:38 -0300 Subject: [PATCH 54/87] deprecating 'line' parameter for shapes --- docs/scarpet/api/Auxiliary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 3a0bbc30..9148fb78 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -80,7 +80,7 @@ Optional shared shape attributes: in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`. * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified player, otherwise it will be send to all players in the dimension. - * `line` - line thickness, defaults to 2.0pt + * `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer. * `fill` - color for the faces, defaults to no fill. Use `color` attribute format * `follow` - entity, or player name. Shape will follow an entity instead of being static. Follow attribute requires all positional arguments to be relative to the entity and disallow From 24e7c0fab9b48c4c3d92fa11100827654f8e9f8f Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 18 Mar 2021 18:06:21 -0300 Subject: [PATCH 55/87] command adding is due to the app refactor --- .../java/carpet/script/CarpetScriptHost.java | 105 ++++++++++++++ .../carpet/script/CarpetScriptServer.java | 129 +++--------------- 2 files changed, 126 insertions(+), 108 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index c79477c0..2432024a 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -20,6 +20,7 @@ import carpet.script.value.Value; import carpet.utils.Messenger; import com.google.gson.JsonElement; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; @@ -32,6 +33,7 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; import org.apache.commons.lang3.tuple.Pair; @@ -43,11 +45,13 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.Math.max; +import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.server.command.CommandManager.literal; public class CarpetScriptHost extends ScriptHost @@ -311,6 +315,107 @@ public class CarpetScriptHost extends ScriptHost } } + public Boolean addAppCommands(Consumer notifier) + { + if (appConfig.get(StringValue.of("commands")) != null) + { + try + { + LiteralArgumentBuilder command = readCommands(commandValidator); + if (command != null) + { + scriptServer.server.getCommandManager().getDispatcher().register(command); + return true; + } + else { + return false; + } + } + catch (CommandSyntaxException cse) + { + // failed + notifier.accept(Messenger.c("r Failed to build command system for "+getName()+" thus failed to load the app ", cse.getRawMessage())); + return null; + } + + } + return addLegacyCommand(notifier); + } + + private Boolean addLegacyCommand(Consumer notifier) + { + if (main == null) return false; + if (getFunction("__command") == null) return false; + + if (scriptServer.isValidCommandRoot(getName())) + { + notifier.accept(Messenger.c("gi Tried to mask vanilla command.")); + return null; + } + + Function configValidator; + try + { + configValidator = getCommandConfigPermissions(); + } + catch (CommandSyntaxException e) + { + notifier.accept(Messenger.c("rb "+e.getMessage())); + return null; + } + String hostName = getName(); + LiteralArgumentBuilder command = literal(hostName). + requires((player) -> scriptServer.modules.containsKey(hostName) && commandValidator.apply(player) && configValidator.apply(player)). + executes( (c) -> + { + CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, ""); + if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); + return (int)response.readInteger(); + }); + + for (String function : globaFunctionNames(main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList())) + { + if (appConfig.getOrDefault(StringValue.of("legacy_command_type_support"), Value.FALSE).getBoolean()) + { + try + { + FunctionValue functionValue = getFunction(function); + command = addPathToCommand( + command, + CommandToken.parseSpec(CommandToken.specFromSignature(functionValue), this), + FunctionArgument.fromCommandSpec(this, functionValue) + ); + } + catch (CommandSyntaxException e) + { + return false; + } + } + else + { + command = command. + then(literal(function). + requires((player) -> scriptServer.modules.containsKey(hostName) && scriptServer.modules.get(hostName).getFunction(function) != null). + executes((c) -> { + CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, ""); + if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString()); + return (int) response.readInteger(); + }). + then(argument("args...", StringArgumentType.greedyString()). + executes( (c) -> { + CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args...")); + if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); + return (int)response.readInteger(); + }))); + } + } + scriptServer.server.getCommandManager().getDispatcher().register(command); + return true; + } + public LiteralArgumentBuilder readCommands(Function useValidator) throws CommandSyntaxException { Value commands = appConfig.get(StringValue.of("commands")); diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index fc9eb5e2..8a93f8c2 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -272,35 +272,23 @@ public class CarpetScriptServer } //addEvents(source, name); String action = reload?"reloaded":"loaded"; - if (newHost.appConfig.get(StringValue.of("commands")) != null) - { - try - { - LiteralArgumentBuilder command = newHost.readCommands(commandValidator); - if (command != null) - { - if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action+" with /"+name+" command"); - server.getCommandManager().getDispatcher().register(command); - CarpetServer.settingsManager.notifyPlayersCommandsChanged(); - } - else { - if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action); - } - } - catch (CommandSyntaxException cse) - { - removeScriptHost(source, name, false, false); - Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app ", cse.getRawMessage()); - return false; - } - - } - else if (!addLegacyCommand(source, name, reload, !isRuleApp, commandValidator)) // this needs to be moved to config reader, only supporting legacy command here + Boolean isCommandAdded = newHost.addAppCommands(s -> { + if (!isRuleApp) Messenger.m(source, s); + }); + if (isCommandAdded == null) // error should be dispatched { removeScriptHost(source, name, false, false); - Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app"); - return false; } + else if (isCommandAdded) + { + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); + if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action+" with /"+name+" command"); + } + else + { + if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action); + } + if (newHost.isPerUser()) { // that will provide player hosts right at the startup @@ -317,89 +305,9 @@ public class CarpetScriptServer return true; } - private boolean addLegacyCommand(ServerCommandSource source, String hostName, boolean isReload, boolean notifySource, Function useValidator) + public boolean isValidCommandRoot(String appName) { - CarpetScriptHost host = modules.get(hostName); - String loaded = isReload?"reloaded":"loaded"; - if (host == null) - { - return true; - } - if (host.getFunction("__command") == null) - { - if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+"."); - return true; - } - if (holyMoly.contains(hostName)) - { - Messenger.m(source, "gi "+hostName+" app "+loaded+" with no command."); - Messenger.m(source, "gi Tried to mask vanilla command."); - return true; - } - - Function configValidator; - try - { - configValidator = host.getCommandConfigPermissions(); - } - catch (CommandSyntaxException e) - { - Messenger.m(source, "rb "+e.getMessage()); - return false; - } - - LiteralArgumentBuilder command = literal(hostName). - requires((player) -> modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)). - executes( (c) -> - { - CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); - Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, ""); - if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); - return (int)response.readInteger(); - }); - - for (String function : host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList())) - { - if (host.appConfig.getOrDefault(StringValue.of("legacy_command_type_support"), Value.FALSE).getBoolean()) - { - try - { - FunctionValue functionValue = host.getFunction(function); - command = host.addPathToCommand( - command, - CommandToken.parseSpec(CommandToken.specFromSignature(functionValue), host), - FunctionArgument.fromCommandSpec(host, functionValue) - ); - } - catch (CommandSyntaxException e) - { - return false; - } - } - else - { - command = command. - then(literal(function). - requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null). - executes((c) -> { - CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); - Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, ""); - if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString()); - return (int) response.readInteger(); - }). - then(argument("args...", StringArgumentType.greedyString()). - executes( (c) -> { - CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); - Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args...")); - if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); - return (int)response.readInteger(); - }))); - } - } - if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+" with /"+hostName+" command"); - server.getCommandManager().getDispatcher().register(command); - CarpetServer.settingsManager.notifyPlayersCommandsChanged(); - return true; + return !holyMoly.contains(appName); } @@ -558,4 +466,9 @@ public class CarpetScriptServer init(); apps.forEach((s, data) -> addScriptHost(server.getCommandSource(), s,data.commandValidator, data.perUser,false, data.isRuleApp)); } + + public void reAddCommands() + { + modules.values().forEach(host -> host.addAppCommands(s -> {})); + } } From f8208c83347023bb342b68d2928fb2407c873704 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 18 Mar 2021 18:54:15 -0300 Subject: [PATCH 56/87] fixed commands disappearing when using datapack command or create_datapack --- .../carpet/mixins/PlayerManager_scarpetEventsMixin.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java b/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java index 0d2d1c96..195dad23 100644 --- a/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_scarpetEventsMixin.java @@ -1,11 +1,13 @@ package carpet.mixins; +import carpet.CarpetServer; import carpet.fakes.ServerPlayerEntityInterface; import net.minecraft.server.PlayerManager; import net.minecraft.server.network.ServerPlayerEntity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import static carpet.script.CarpetEventServer.Event.PLAYER_RESPAWNS; @@ -25,4 +27,9 @@ public class PlayerManager_scarpetEventsMixin ((ServerPlayerEntityInterface)player).invalidateEntityObjectReference(); } + @Inject(method = "onDataPacksReloaded", at = @At("HEAD")) + private void reloadCommands(CallbackInfo ci) + { + CarpetServer.scriptServer.reAddCommands(); + } } From 1a47f4333fe354bf1f8fec2ee534f9c5f8ebe01d Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 20 Mar 2021 06:22:23 -0300 Subject: [PATCH 57/87] refactored file IO and zupport for sips --- docs/scarpet/api/Auxiliary.md | 9 +- .../language/FunctionsAndControlFlow.md | 4 +- .../java/carpet/script/CarpetScriptHost.java | 53 +-- .../carpet/script/CarpetScriptServer.java | 4 +- .../java/carpet/script/api/Auxiliary.java | 112 ++---- .../carpet/script/argument/FileArgument.java | 347 +++++++++++++----- .../carpet/script/bundled/FileModule.java | 174 --------- .../java/carpet/script/bundled/Module.java | 108 +----- .../carpet/script/exception/Throwables.java | 4 +- .../java/carpet/script/language/Loops.java | 2 +- 10 files changed, 335 insertions(+), 482 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 9148fb78..952e0f15 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -298,7 +298,8 @@ Resource is identified by a path to the file. A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. Do not add file extensions to the descriptor - extensions are inferred -based on the `type` of the file. +based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from +zip files, although you cannot nest zip files in other zip files. Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning @@ -312,7 +313,7 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s The default no-name app, via `/script run` command can only save/load/read files from the shared space. -Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true` +Functions return `null` if no file is present (for read, list and delete operations). Returns `true` for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files for folder listing. @@ -336,6 +337,10 @@ third argument. Throws: - `nbt_read_error`: When failed to read NBT file. - `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +- `io_exception`: For all other errors when handling data on disk not related to encoding issues + +All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception +thrown.
 write_file('foo', 'shared_text, ['one', 'two']);
diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md
index a02c2d51..a0a867e0 100644
--- a/docs/scarpet/language/FunctionsAndControlFlow.md
+++ b/docs/scarpet/language/FunctionsAndControlFlow.md
@@ -265,8 +265,8 @@ but like everywhere else, doing that sounds like a bad idea.
     `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific 
     errors thrown when a specified internal name does not exist or is invalid.
   - `io_exception`: This is the parent for any exception that occurs due to an error handling external data.
-    - `nbt_read_error`: Incorrect input NBT file.
-    - `json_read_error`: Incorrect input JSON data.
+    - `nbt_error`: Incorrect input/output NBT file.
+    - `json_error`: Incorrect input/output JSON data.
   - `user_exception`: Exception thrown by default with `throw` function.
   
 Synopsis:
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index 2432024a..b6dc0e85 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -3,6 +3,7 @@ package carpet.script;
 import carpet.CarpetServer;
 import carpet.CarpetSettings;
 import carpet.script.api.Auxiliary;
+import carpet.script.argument.FileArgument;
 import carpet.script.argument.FunctionArgument;
 import carpet.script.bundled.Module;
 import carpet.script.command.CommandArgument;
@@ -347,7 +348,7 @@ public class CarpetScriptHost extends ScriptHost
         if (main == null) return false;
         if (getFunction("__command") == null) return false;
 
-        if (scriptServer.isValidCommandRoot(getName()))
+        if (scriptServer.isInvalidCommandRoot(getName()))
         {
             notifier.accept(Messenger.c("gi Tried to mask vanilla command."));
             return null;
@@ -785,31 +786,31 @@ public class CarpetScriptHost extends ScriptHost
 
     private void dumpState()
     {
-        Module.saveData(main, null, globalState, false);
+        Module.saveData(main, globalState);
     }
 
     private Tag loadState()
     {
-        return Module.getData(main, null, false);
+        return Module.getData(main);
     }
 
-    public Tag readFileTag(String file, boolean isShared)
+    public Tag readFileTag(FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return null;
-        if (file != null)
-            return Module.getData(main, file, isShared);
+        if (getName() == null && !fdesc.isShared) return null;
+        if (fdesc.resource != null)
+            return fdesc.getNbtData(main);
         if (parent == null)
             return globalState;
         return ((CarpetScriptHost)parent).globalState;
     }
 
-    public boolean writeTagFile(Tag tag, String file, boolean isShared)
+    public boolean writeTagFile(Tag tag, FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
+        if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
 
-        if (file!= null)
+        if (fdesc.resource != null)
         {
-            return Module.saveData(main, file, tag, isShared);
+            return fdesc.saveNbtData(main, tag);
         }
 
         CarpetScriptHost responsibleHost = (parent != null)?(CarpetScriptHost) parent:this;
@@ -822,34 +823,34 @@ public class CarpetScriptHost extends ScriptHost
         return true;
     }
 
-    public boolean removeResourceFile(String resource, boolean isShared, String type)
+    public boolean removeResourceFile(FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return false; //
-        return Module.dropExistingFile(main, resource, type, isShared);
+        if (getName() == null && !fdesc.isShared) return false; //
+        return fdesc.dropExistingFile(main);
     }
 
-    public boolean appendLogFile(String resource, boolean isShared, String type, List data)
+    public boolean appendLogFile(FileArgument fdesc, List data)
     {
-        if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
-        return Module.appendToTextFile(main, resource, type, isShared, data);
+        if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
+        return fdesc.appendToTextFile(main, data);
     }
 
-    public List readTextResource(String resource, String type, boolean isShared)
+    public List readTextResource(FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return null;
-        return Module.listFile(main, resource, type, isShared);
+        if (getName() == null && !fdesc.isShared) return null;
+        return fdesc.listFile(main);
     }
     
-    public JsonElement readJsonFile(String resource, String type, boolean isShared)
+    public JsonElement readJsonFile(FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return null;
-        return Module.readJsonFile(main, resource, type, isShared);
+        if (getName() == null && !fdesc.isShared) return null;
+        return fdesc.readJsonFile(main);
     }
 
-    public Stream listFolder(String resource, String type, boolean isShared)
+    public Stream listFolder(FileArgument fdesc)
     {
-        if (getName() == null && !isShared) return null; //
-        return Module.listFolder(main, resource, type, isShared);
+        if (getName() == null && !fdesc.isShared) return null; //
+        return fdesc.listFolder(main);
     }
 
 
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 8a93f8c2..cd02173b 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -305,9 +305,9 @@ public class CarpetScriptServer
         return true;
     }
 
-    public boolean isValidCommandRoot(String appName)
+    public boolean isInvalidCommandRoot(String appName)
     {
-        return !holyMoly.contains(appName);
+        return holyMoly.contains(appName);
     }
 
 
diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index a1aaec2f..f540e468 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -7,7 +7,7 @@ import carpet.fakes.ServerWorldInterface;
 import carpet.fakes.StatTypeInterface;
 import carpet.fakes.ThreadedAnvilChunkStorageInterface;
 import carpet.helpers.FeatureGenerator;
-import carpet.script.bundled.Module;
+import carpet.script.argument.FileArgument;
 import carpet.script.CarpetContext;
 import carpet.script.CarpetEventServer;
 import carpet.script.CarpetScriptHost;
@@ -99,7 +99,6 @@ import net.minecraft.world.level.UnmodifiableLevelProperties;
 import net.minecraft.world.level.storage.LevelStorage;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.commons.lang3.tuple.Triple;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -133,6 +132,7 @@ public class Auxiliary {
     private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
     public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
 
+    @Deprecated
     public static String recognizeResource(Value value, boolean isFloder)
     {
         String origfile = value.getString();
@@ -145,20 +145,6 @@ public class Auxiliary {
         return file;
     }
 
-    public static Triple getFileDescriptor(List lv, Context c, boolean isFloder)
-    {
-        if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
-        String resource = recognizeResource(lv.get(0).evalValue(c), isFloder);
-        String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
-        boolean shared = origtype.startsWith("shared_");
-        String type = shared ? origtype.substring(7) : origtype; //len(shared_)
-        if (!Module.supportedTypes.containsKey(type))
-            throw new InternalExpressionException("Unsupported file type: "+origtype);
-        if (type.equals("folder") && !isFloder)
-            throw new InternalExpressionException("Folder types are no supported for this IO function");
-        return Triple.of(resource, type, shared);
-    }
-
     public static void apply(Expression expression)
     {
         expression.addLazyFunction("sound", -1, (c, t, lv) -> {
@@ -854,49 +840,27 @@ public class Auxiliary {
 
         expression.addLazyFunction("list_files", 2, (c, t, lv) ->
         {
-            Triple fdesc = getFileDescriptor(lv, c, true);
-            Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
+            FileArgument fdesc = FileArgument.from(lv, c, true, FileArgument.Reason.READ);
+            Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc);
             if (files == null) return LazyValue.NULL;
             Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
             return (cc, tt) -> ret;
         });
 
-
         expression.addLazyFunction("read_file", 2, (c, t, lv) ->
         {
-            Triple fdesc = getFileDescriptor(lv, c, false);
+            FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.READ);
             Value retVal;
-            if (fdesc.getMiddle().equals("nbt"))
+            if (fdesc.type == FileArgument.Type.NBT)
             {
-                Tag state;
-                try
-                {
-                    state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
-                } catch (CrashException e)
-                {
-                    throw new ThrowStatement((fdesc.getRight()?"shared/":"")+ fdesc.getLeft(), Throwables.NBT_READ);
-                }
-
+                Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
                 if (state == null) return LazyValue.NULL;
                 retVal = new NBTSerializableValue(state);
             }
-            else if (fdesc.getMiddle().equals("json"))
+            else if (fdesc.type == FileArgument.Type.JSON)
             {
                 JsonElement json;
-                try
-                {
-                    json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
-                }
-                catch (JsonParseException e)
-                {
-                    Throwable exc = e;
-                    if(e.getCause() != null)
-                        exc = e.getCause();
-                    throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
-                            StringValue.of("error"), StringValue.of(exc.getMessage()),
-                            StringValue.of("path"), StringValue.of((fdesc.getRight()?"shared/":"")+ fdesc.getLeft())
-                    )), Throwables.JSON_READ);
-                }
+                json = ((CarpetScriptHost) c.host).readJsonFile(fdesc);
                 Value parsedJson = GSON.fromJson(json, Value.class);
                 if (parsedJson == null)
                     retVal = Value.NULL;
@@ -905,7 +869,7 @@ public class Auxiliary {
             }
             else
             {
-                List content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
+                List content = ((CarpetScriptHost) c.host).readTextResource(fdesc);
                 if (content == null) return LazyValue.NULL;
                 retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
             }
@@ -913,32 +877,29 @@ public class Auxiliary {
         });
 
         expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
-            Triple fdesc = getFileDescriptor(lv, c, false);
-            return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
-                    ? LazyValue.TRUE
-                    : LazyValue.FALSE;
+            FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.DELETE);
+            return ((CarpetScriptHost) c.host).removeResourceFile(fdesc) ? LazyValue.TRUE : LazyValue.FALSE;
         });
 
         expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
             if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
-            Triple fdesc = getFileDescriptor(lv, c, false);
-
+            FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.CREATE);
 
             boolean success;
-            if (fdesc.getMiddle().equals("nbt"))
+            if (fdesc.type == FileArgument.Type.NBT)
             {
                 Value val = lv.get(2).evalValue(c);
                 NBTSerializableValue tagValue =  (val instanceof NBTSerializableValue)
                         ? (NBTSerializableValue) val
                         : new NBTSerializableValue(val.getString());
                 Tag tag = tagValue.getTag();
-                success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
+                success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
             }
-            else if (fdesc.getMiddle().equals("json"))
+            else if (fdesc.type == FileArgument.Type.JSON)
             {
                 List data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson()));
-                ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle());
-                success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
+                ((CarpetScriptHost) c.host).removeResourceFile(fdesc);
+                success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
             }
             else
             {
@@ -963,34 +924,22 @@ public class Auxiliary {
                         data.add(lv.get(i).evalValue(c).getString());
                     }
                 }
-                success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
+                success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
             }
             return success?LazyValue.TRUE:LazyValue.FALSE;
         });
 
-        //write_file
-
         expression.addLazyFunction("load_app_data", -1, (c, t, lv) ->
         {
-            String file = null;
-            boolean shared = false;
+            FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.READ);
             if (lv.size()>0)
             {
                 c.host.issueDeprecation("load_app_data(...) with arguments");
-                file = recognizeResource(lv.get(0).evalValue(c), false);
-                if (lv.size() > 1)
-                {
-                    shared = lv.get(1).evalValue(c).getBoolean();
-                }
-            }
-            Tag state;
-            try
-            {
-                state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
-            } catch (CrashException e)
-            {
-                throw new ThrowStatement("app data", Throwables.NBT_READ);
+                String resource = recognizeResource(lv.get(0).evalValue(c), false);
+                boolean shared = lv.size() > 1 && lv.get(1).evalValue(c).getBoolean();
+                fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.READ);
             }
+            Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
             if (state == null)
                 return (cc, tt) -> Value.NULL;
             Value retVal = new NBTSerializableValue(state);
@@ -1002,22 +951,19 @@ public class Auxiliary {
             if (lv.size() == 0)
                 throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file");
             Value val = lv.get(0).evalValue(c);
-            String file = null;
-            boolean shared = false;
+            FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.CREATE);
             if (lv.size()>1)
             {
                 c.host.issueDeprecation("store_app_data(...) with more than one argument");
-                file = recognizeResource(lv.get(1).evalValue(c), false);
-                if (lv.size() > 2)
-                {
-                    shared = lv.get(2).evalValue(c).getBoolean();
-                }
+                String resource = recognizeResource(lv.get(1).evalValue(c), false);
+                boolean shared = lv.size() > 2 && lv.get(2).evalValue(c).getBoolean();
+                fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.CREATE);
             }
             NBTSerializableValue tagValue =  (val instanceof NBTSerializableValue)
                     ? (NBTSerializableValue) val
                     : new NBTSerializableValue(val.getString());
             Tag tag = tagValue.getTag();
-            boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared);
+            boolean success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
             return success?LazyValue.TRUE:LazyValue.FALSE;
         });
 
diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java
index f3ad42fe..8fbef1c5 100644
--- a/src/main/java/carpet/script/argument/FileArgument.java
+++ b/src/main/java/carpet/script/argument/FileArgument.java
@@ -5,34 +5,47 @@ import carpet.script.Context;
 import carpet.script.LazyValue;
 import carpet.script.bundled.Module;
 import carpet.script.exception.InternalExpressionException;
+import carpet.script.exception.ThrowStatement;
+import carpet.script.exception.Throwables;
+import carpet.script.value.MapValue;
+import carpet.script.value.StringValue;
 import carpet.script.value.Value;
+import com.google.common.collect.ImmutableMap;
 import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
 import com.google.gson.JsonParser;
 import com.google.gson.stream.JsonReader;
+import com.sun.nio.zipfs.ZipPath;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.NbtIo;
 import net.minecraft.nbt.PositionTracker;
 import net.minecraft.nbt.Tag;
 import net.minecraft.nbt.TagReaders;
 import net.minecraft.util.WorldSavePath;
+import net.minecraft.util.crash.CrashException;
 import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.tuple.Pair;
 
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
+import java.net.URI;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -47,9 +60,22 @@ public class FileArgument
     public boolean isFolder;
     public boolean isShared;
     public Reason reason;
+    private FileSystem zfs;
+    private Path zipPath;
 
     public final static Object writeIOSync = new Object();
 
+    public void close() {
+        if (zfs != null && zfs.isOpen()) {
+            try {
+                zfs.close();
+            } catch (IOException e) {
+                throw new InternalExpressionException("Unable to close zip container: "+zipContainer);
+            }
+            zfs = null;
+        }
+    }
+
     public enum Type
     {
         RAW("raw", ".txt"),
@@ -75,7 +101,7 @@ public class FileArgument
         READ, CREATE, DELETE
     }
 
-    private FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason)
+    public FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason)
     {
         this.resource = resource;
         this.type = type;
@@ -83,47 +109,103 @@ public class FileArgument
         this.isFolder = isFolder;
         this.isShared = isShared;
         this.reason = reason;
+        this.zfs = null;
+        this.zipPath = null;
+    }
+
+    @Override
+    public String toString() {
+        return "path: "+resource+" zip: "+zipContainer+" type: "+type.id+" folder: "+isFolder+" shared: "+isShared+" reason: "+reason.toString();
     }
 
     public static FileArgument from(List lv, Context c, boolean isFolder, Reason reason)
     {
         if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
-        String resource = recognizeResource(lv.get(0).evalValue(c), isFolder);
+        Pair resource = recognizeResource(lv.get(0).evalValue(c), isFolder);
         String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
         boolean shared = origtype.startsWith("shared_");
         String typeString = shared ? origtype.substring(7) : origtype; //len(shared_)
         Type type = Type.of.get(typeString);
-        if (!Type.of.containsKey(typeString))
+        if (type==null)
             throw new InternalExpressionException("Unsupported file type: "+origtype);
-
         if (type==Type.FOLDER && !isFolder)
             throw new InternalExpressionException("Folder types are no supported for this IO function");
-        return  new FileArgument(resource, type,null, isFolder, shared, reason);
+        return  new FileArgument(resource.getLeft(), type,resource.getRight(), isFolder, shared, reason);
 
     }
 
-    public static String recognizeResource(Value value, boolean isFolder)
+    public static Pair recognizeResource(Value value, boolean isFolder)
     {
         String origfile = value.getString();
-        String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", "");
-        file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
-        if (file.isEmpty() && !isFolder)
+        String[] pathElements = origfile.toLowerCase(Locale.ROOT).split("[/\\\\]+");
+        List path = new ArrayList<>();
+        String zipPath = null;
+        for (String token : pathElements)
         {
-            throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers");
+            boolean isZip = token.endsWith(".zip");
+            if (zipPath != null && isZip) throw new InternalExpressionException(token+" indicates zip access in an already zipped location "+zipPath);
+            if (isZip) token = token.substring(0, token.length()-4);
+            token = token.replaceAll("[^A-Za-z0-9\\-+_]", "");
+            if (token.isEmpty()) continue;
+            if (isZip) token = token + ".zip";
+            path.add(token);
+            if (isZip)
+            {
+                zipPath = String.join("/", path);
+                path.clear();
+            }
         }
-        return file;
+        if (path.isEmpty() && !isFolder)
+            throw new InternalExpressionException(
+                    "Cannot use "+origfile+" as resource name: indicated path is empty"+((zipPath==null)?"":" in zip container "+zipPath)
+            );
+        return Pair.of(String.join("/", path), zipPath);
+    }
+
+    private Path resolve(String suffix)
+    {
+        if (CarpetServer.minecraft_server == null)
+            throw new InternalExpressionException("Accessing world files without server running");
+        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+suffix);
     }
 
     private Path toPath(Module module)
     {
         if (!isShared && (module == null || module.getName() == null)) return null;
-        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resource)+type.extension);
+        if (zipContainer == null)
+            return resolve(getDescriptor(module, resource)+(isFolder?"":type.extension));
+        else
+        {
+            if (zfs == null)
+            {
+                Map env = new HashMap<>();
+                if (reason == Reason.CREATE) env.put("create", "true");
+                zipPath = resolve(getDescriptor(module, zipContainer));
+                try {
+                    zfs = FileSystems.newFileSystem(URI.create("jar:"+ zipPath.toUri().toString()), env);
+                }
+                catch (FileSystemNotFoundException fsnfe)
+                {
+                    return null;
+                }
+                catch (IOException e)
+                {
+                    throw new ThrowStatement("Unable to open zip file: "+zipContainer, Throwables.IO_EXCEPTION);
+                }
+            }
+            return zfs.getPath(resource+(isFolder?"/":type.extension));
+        }
     }
 
     private Path moduleRootPath(Module module)
     {
         if (!isShared && (module == null || module.getName() == null)) return null;
-        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, ""));
+        return resolve(isShared?"shared":module.getName()+".data");
+    }
+
+    public String getDisplayPath()
+    {
+        return (isShared?"shared/":"")+(zipContainer!=null?zipContainer+"/":"")+resource+type.extension;
     }
 
     private String getDescriptor(Module module, String res)
@@ -136,7 +218,7 @@ public class FileArgument
         {
             return module.getName()+".data"+(res==null || res.isEmpty()?"":"/"+res);
         }
-        throw  new RuntimeException("Invalid file descriptor: "+res);
+        throw new InternalExpressionException("Invalid file descriptor: "+res);
     }
 
     public Stream listFiles(Module module)
@@ -146,8 +228,7 @@ public class FileArgument
         String ext = type.extension;
         try
         {
-            return Files.list(dir).
-                    filter(path -> isFolder?Files.isDirectory(path):path.toString().endsWith(ext));
+            return Files.list(dir).filter(path -> (type==Type.FOLDER)?path.toString().endsWith("/"):path.toString().endsWith(ext));
         }
         catch (IOException ignored)
         {
@@ -157,73 +238,106 @@ public class FileArgument
 
     public Stream listFolder(Module module)
     {
-        Stream result;
-        synchronized (writeIOSync) { result = listFiles(module); }
-        if (result == null) return null;
-        Path rootPath = moduleRootPath(module);
-        Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
-        if (type != Type.FOLDER)
-            return strings.map(FilenameUtils::removeExtension);
-        return strings;
+        Stream strings;
+        try { synchronized (writeIOSync)
+        {
+            Stream result;
+            result = listFiles(module);
+            if (result == null) return null;
+            Path rootPath = moduleRootPath(module);
+            if (rootPath == null) return null;
+            String zipComponent = (zipContainer != null) ? rootPath.relativize(zipPath).toString() : null;
+            strings = result.map(p -> {
+                if (zipContainer == null)
+                    return rootPath.relativize(p).toString().replaceAll("[\\\\/]+", "/");
+                return (zipComponent + p.toString()).replaceAll("[\\\\/]+", "/");
+            });
+        } }
+        finally
+        {
+            close();
+        }
+        if (type == Type.FOLDER)
+            return strings.map(s -> s.endsWith("/")?s.substring(0, s.length()-2):s);
+        return strings.map(FilenameUtils::removeExtension);
+    }
+
+    private void createPaths(Path file)
+    {
+        try {
+            if ((zipContainer == null || file.getParent() != null) &&
+                    !Files.exists(file.getParent()) &&
+                    Files.createDirectories(file.getParent()) == null
+            ) throw new IOException();
+        } catch (IOException e) {
+            throw new ThrowStatement("Unable to create paths for "+file.toString(), Throwables.IO_EXCEPTION);
+        }
+
     }
 
     public boolean appendToTextFile(Module module, List message)
     {
-        Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared);
-        if (dataFile == null) return false;
-        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync)
+        try { synchronized (writeIOSync)
         {
-            try
+            Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared);
+            if (dataFile == null) return false;
+            createPaths(dataFile);
+            OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
+            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
             {
-                OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
-                try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
+                for (String line: message)
                 {
-                    for (String line: message)
-                    {
-                        writer.append(line);
-                        if (type == Type.TEXT) writer.newLine();
-                    }
+                    writer.append(line);
+                    if (type == Type.TEXT) writer.newLine();
                 }
             }
-            catch (IOException e)
-            {
-                return false;
-            }
-            return true;
+        } }
+        catch (IOException e)
+        {
+            throw new ThrowStatement("Error when writing to the file: "+e, Throwables.IO_EXCEPTION);
         }
+        finally
+        {
+            close();
+        }
+        return true;
     }
 
     public Tag getNbtData(Module module) // aka getData
     {
-        Path dataFile = toPath(module);
-        if (dataFile == null) return null;
-        if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
-        synchronized (writeIOSync) { return readTag(dataFile.toFile()); }
+        try { synchronized (writeIOSync) {
+            Path dataFile = toPath(module);
+            if (dataFile == null || !Files.exists(dataFile)) return null;
+            return readTag(dataFile);
+        } }
+        finally {
+            close();
+        }
     }
 
     //copied private method from net.minecraft.nbt.NbtIo.read()
     // to read non-compound tags - these won't be compressed
-    public static Tag readTag(File file)
+    public static Tag readTag(Path path)
     {
         try
         {
-            return NbtIo.readCompressed(file);
+            return NbtIo.readCompressed(Files.newInputStream(path));
         }
         catch (IOException e)
         {
             // Copy of NbtIo.read(File) because that's now client-side only
-            if (!file.exists())
+            if (!Files.exists(path))
             {
                 return null;
             }
-            try (DataInputStream in = new DataInputStream(new FileInputStream(file)))
+            try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
             {
                 return NbtIo.read(in);
             }
             catch (IOException ioException)
             {
-                try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
+                // not compressed compound tag neither uncompressed compound tag - trying any type of a tag
+                try (DataInputStream dataInput_1 = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
                 {
                     byte byte_1 = dataInput_1.readByte();
                     if (byte_1 == 0)
@@ -238,71 +352,102 @@ public class FileArgument
                 }
                 catch (IOException ignored)
                 {
+                    throw new ThrowStatement("Not a valid NBT tag in "+path.toString(), Throwables.NBT_ERROR);
                 }
-                return null;
             }
         }
+        catch (CrashException e)
+        {
+            throw new ThrowStatement("Error when reading NBT file "+path.toString(), Throwables.NBT_ERROR);
+        }
     }
 
     public boolean saveNbtData(Module module, Tag tag) // aka saveData
     {
-        Path dataFile = toPath(module);
-        if (dataFile == null) return false;
-        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync) { return writeTag(tag, dataFile.toFile()); }
+        try { synchronized (writeIOSync) {
+            Path dataFile = toPath(module);
+            if (dataFile == null) return false;
+            createPaths(dataFile);
+            return writeTagDisk(tag, dataFile);
+        } }
+        finally
+        {
+            close();
+        }
     }
 
     //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
-    public static boolean writeTag(Tag tag_1, File file)
+    public static boolean writeTagDisk(Tag tag_1, Path path)
     {
-        File file_2 = new File(file.getAbsolutePath() + "_tmp");
-        if (file_2.exists()) file_2.delete();
+        Path original = path;
+        try
+        {
+            boolean zipped = path instanceof ZipPath;
+            if (!zipped)
+            {
+                path = new File(path.toFile().getAbsolutePath() + "_tmp").toPath();
+                Files.deleteIfExists(path);
+            }
 
-        if (tag_1 instanceof CompoundTag)
-        {
-            try
+            if (tag_1 instanceof CompoundTag)
             {
-                NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
+                NbtIo.writeCompressed((CompoundTag) tag_1, Files.newOutputStream(path));
             }
-            catch (IOException e)
+            else
             {
-                return false;
-            }
-        }
-        else
-        {
-            try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2)))
-            {
-                dataOutputStream_1.writeByte(tag_1.getType());
-                if (tag_1.getType() != 0)
+                try (DataOutputStream dataOutputStream_1 = new DataOutputStream(Files.newOutputStream(path)))
                 {
-                    dataOutputStream_1.writeUTF("");
-                    tag_1.write(dataOutputStream_1);
+                    dataOutputStream_1.writeByte(tag_1.getType());
+                    if (tag_1.getType() != 0)
+                    {
+                        dataOutputStream_1.writeUTF("");
+                        tag_1.write(dataOutputStream_1);
+                    }
                 }
             }
-            catch (IOException e)
+            if (!zipped)
             {
-                return false;
+                Files.deleteIfExists(original);
+                Files.move(path, original);
             }
+            return true;
+        }
+        catch (IOException e)
+        {
+            throw new ThrowStatement("Unable to write tag to "+original.toString(), Throwables.IO_EXCEPTION);
         }
-        if (file.exists()) file.delete();
-        if (!file.exists()) file_2.renameTo(file);
-        return true;
     }
 
     public boolean dropExistingFile(Module module)
     {
-        Path dataFile = toPath(module);
-        if (dataFile == null) return false;
-        synchronized (writeIOSync) { return dataFile.toFile().delete(); }
+
+        try { synchronized (writeIOSync)
+        {
+            Path dataFile = toPath(module);
+            if (dataFile == null) return false;
+            return Files.deleteIfExists(dataFile);
+        } }
+        catch (IOException e)
+        {
+            throw new ThrowStatement("Error while removing file: "+getDisplayPath(), Throwables.IO_EXCEPTION);
+        }
+        finally
+        {
+            close();
+        }
     }
 
     public List listFile(Module module)
     {
-        Path dataFile = toPath(module);
-        if (dataFile == null) return null;
-        if (!dataFile.toFile().exists()) return null;
-        synchronized (writeIOSync) { return listFileContent(dataFile); }
+        try { synchronized (writeIOSync) {
+            Path dataFile = toPath(module);
+            if (dataFile == null) return null;
+            if (!Files.exists(dataFile)) return null;
+            return listFileContent(dataFile);
+        } }
+        finally {
+            close();
+        }
     }
 
     public static List listFileContent(Path filePath)
@@ -319,15 +464,19 @@ public class FileArgument
         }
         catch (IOException e)
         {
-            return null;
+            throw new ThrowStatement("Failed to read text file "+filePath.toString(), Throwables.IO_EXCEPTION);
         }
     }
 
     public JsonElement readJsonFile(Module module) {
-        Path dataFile = toPath(module);
-        if (dataFile == null) return null;
-        if (!dataFile.toFile().exists()) return null;
-        synchronized (writeIOSync) { return readJsonContent(dataFile); }
+        try { synchronized (writeIOSync) {
+            Path dataFile = toPath(module);
+            if (dataFile == null || !Files.exists(dataFile)) return null;
+            return readJsonContent(dataFile);
+        } }
+        finally {
+            close();
+        }
     }
 
     public static JsonElement readJsonContent(Path filePath)
@@ -336,9 +485,19 @@ public class FileArgument
         {
             return new JsonParser().parse(new JsonReader(reader));
         }
+        catch (JsonParseException e)
+        {
+            Throwable exc = e;
+            if(e.getCause() != null)
+                exc = e.getCause();
+            throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
+                    StringValue.of("error"), StringValue.of(exc.getMessage()),
+                    StringValue.of("path"), StringValue.of(filePath.toString())
+            )), Throwables.JSON_ERROR);
+        }
         catch (IOException e)
         {
-            return null;
+            throw new ThrowStatement("Failed to read json file content "+filePath.toString(), Throwables.IO_EXCEPTION);
         }
     }
 
diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java
index 20cd65af..23d558f5 100644
--- a/src/main/java/carpet/script/bundled/FileModule.java
+++ b/src/main/java/carpet/script/bundled/FileModule.java
@@ -1,33 +1,10 @@
 package carpet.script.bundled;
 
-import net.minecraft.nbt.CompoundTag;
-import net.minecraft.nbt.NbtIo;
-import net.minecraft.nbt.PositionTracker;
-import net.minecraft.nbt.Tag;
-import net.minecraft.nbt.TagReaders;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
-import java.util.stream.Stream;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParser;
-import com.google.gson.stream.JsonReader;
 
 public class FileModule extends Module
 {
@@ -66,155 +43,4 @@ public class FileModule extends Module
         return library;
     }
 
-    //copied private method from net.minecraft.nbt.NbtIo.read()
-    // to read non-compound tags - these won't be compressed
-    @Deprecated
-    public static Tag read(File file)
-    {
-        try
-        {
-            return NbtIo.readCompressed(file);
-        }
-        catch (IOException e)
-        {
-            // Copy of NbtIo.read(File) because that's now client-side only
-            if (!file.exists())
-            {
-                return null;
-            }
-            try (DataInputStream in = new DataInputStream(new FileInputStream(file)))
-            {
-                return NbtIo.read(in);
-            }
-            catch (IOException ioException)
-            {
-                try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
-                {
-                    byte byte_1 = dataInput_1.readByte();
-                    if (byte_1 == 0)
-                    {
-                        return null;
-                    }
-                    else
-                    {
-                        dataInput_1.readUTF();
-                        return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT);
-                    }
-                }
-                catch (IOException ignored)
-                {
-                }
-                return null;
-            }
-        }
-    }
-
-    //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
-    @Deprecated
-    public static boolean write(Tag tag_1, File file)
-    {
-        File file_2 = new File(file.getAbsolutePath() + "_tmp");
-        if (file_2.exists()) file_2.delete();
-
-        if (tag_1 instanceof CompoundTag)
-        {
-            try
-            {
-                NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
-            }
-            catch (IOException e)
-            {
-                return false;
-            }
-        }
-        else
-        {
-            try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2)))
-            {
-                dataOutputStream_1.writeByte(tag_1.getType());
-                if (tag_1.getType() != 0)
-                {
-                    dataOutputStream_1.writeUTF("");
-                    tag_1.write(dataOutputStream_1);
-                }
-            }
-            catch (IOException e)
-            {
-                return false;
-            }
-        }
-        if (file.exists()) file.delete();
-        if (!file.exists()) file_2.renameTo(file);
-        return true;
-    }
-
-    @Deprecated
-    public static boolean appendText(Path filePath, boolean addNewLines, List data)
-    {
-        try
-        {
-            OutputStream out = Files.newOutputStream(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
-            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
-            {
-                for (String line: data)
-                {
-                    writer.append(line);
-                    if (addNewLines) writer.newLine();
-                }
-            }
-        }
-        catch (IOException e)
-        {
-            return false;
-        }
-        return true;
-    }
-
-
-    @Deprecated
-    public static List listFileContent(Path filePath)
-    {
-        try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
-            List result = new ArrayList<>();
-            for (;;) {
-                String line = reader.readLine();
-                if (line == null)
-                    break;
-                result.add(line.replaceAll("[\n\r]+",""));
-            }
-            return result;
-        }
-        catch (IOException e)
-        {
-            return null;
-        }
-    }
-
-    @Deprecated
-    public static JsonElement readJsonContent(Path filePath)
-    {
-    	try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8))
-        {
-            return new JsonParser().parse(new JsonReader(reader));
-        }
-        catch (IOException e)
-        {
-            return null;
-        }
-    }
-
-    @Deprecated
-    public static Stream listFiles(Path dir, String ext)
-    {
-        boolean folderListing = ext.equals("folder");
-        try
-        {
-            return Files.list(dir).
-                    filter(path -> folderListing?Files.isDirectory(path):path.toString().endsWith(ext));
-        }
-        catch (IOException ignored)
-        {
-            return null;
-        }
-    }
 }
diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java
index ea6231b5..f76c4ad8 100644
--- a/src/main/java/carpet/script/bundled/Module.java
+++ b/src/main/java/carpet/script/bundled/Module.java
@@ -1,123 +1,39 @@
 package carpet.script.bundled;
 
 import carpet.CarpetServer;
+import carpet.script.argument.FileArgument;
 import net.minecraft.nbt.Tag;
 import net.minecraft.util.WorldSavePath;
-import org.apache.commons.io.FilenameUtils;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.gson.JsonElement;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
 
 public abstract class Module
 {
     public abstract String getName();
     public abstract String getCode();
     public abstract boolean isLibrary();
-    @Deprecated
-    public final static Object writeIOSync = new Object();
-    @Deprecated
-    public final static Map supportedTypes = ImmutableMap.of(
-            "raw", ".txt",
-            "text", ".txt",
-            "nbt", ".nbt",
-            "json", ".json",
-            "folder", "folder"
-    );
 
-    @Deprecated
-    private static String getDescriptor(Module module, String file, boolean isShared)
+    public static Tag getData(Module module)
     {
-        if (isShared)
-        {
-            return file.isEmpty()?"shared":"shared/"+file;
-        }
-        if (module != null && module.getName() != null) // appdata
-        {
-            return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
-        }
-        throw  new RuntimeException("Invalid file descriptor: "+file);
-    }
-
-    @Deprecated
-    public static Tag getData(Module module, String file, boolean isShared)
-    {
-        Path dataFile = resolveResource(module, file, ".nbt", isShared);
+        Path dataFile = resolveResource(module);
         if (dataFile == null) return null;
         if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
-        synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
+        synchronized (FileArgument.writeIOSync) { return FileArgument.readTag(dataFile); }
     }
 
-    @Deprecated
-    public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
+    public static void saveData(Module module, Tag globalState)
     {
-        Path dataFile = resolveResource(module, file, ".nbt", isShared);
-        if (dataFile == null) return false;
-        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
+        Path dataFile = resolveResource(module);
+        if (dataFile == null) return;
+        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return;
+        synchronized (FileArgument.writeIOSync) { FileArgument.writeTagDisk(globalState, dataFile); }
     }
 
-    @Deprecated
-    public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message)
+    private static Path resolveResource(Module module)
     {
-        Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
-        if (dataFile == null) return false;
-        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
-    }
-
-    @Deprecated
-    public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared)
-    {
-        Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
-        if (dataFile == null) return false;
-        synchronized (writeIOSync) { return dataFile.toFile().delete(); }
-    }
-
-    @Deprecated
-    public static List listFile(Module module, String resourceName, String type, boolean isShared)
-    {
-        Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
-        if (dataFile == null) return null;
-        if (!dataFile.toFile().exists()) return null;
-        synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
-    }
-
-    @Deprecated
-    public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) {
-        Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
-        if (dataFile == null) return null;
-        if (!dataFile.toFile().exists()) return null;
-        synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); }
-    }
-
-    @Deprecated
-    public static Stream listFolder(Module module, String resourceName, String type, boolean isShared)
-    {
-        Path dir = resolveResource(module, resourceName, null, isShared);
-        String ext = supportedTypes.get(type);
-        if (dir == null) return null;
-        if (!Files.exists(dir)) return null;
-        Stream result;
-        synchronized (writeIOSync) { result = FileModule.listFiles(dir, ext); }
-        if (result == null) return null;
-        Path rootPath = resolveResource(module, "", null, isShared);
-        Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
-        if (!ext.equals("folder"))
-            return strings.map(FilenameUtils::removeExtension);
-        return strings;
-    }
-
-    @Deprecated
-    private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
-    {
-        if (!isShared && (module == null || module.getName() == null)) return null;
-        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":ext));
+        if (module == null || module.getName() == null) return null; // commandline app
+        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+module.getName()+".data.nbt");
     }
 
     @Override
diff --git a/src/main/java/carpet/script/exception/Throwables.java b/src/main/java/carpet/script/exception/Throwables.java
index 22857a41..3f811cfe 100644
--- a/src/main/java/carpet/script/exception/Throwables.java
+++ b/src/main/java/carpet/script/exception/Throwables.java
@@ -25,8 +25,8 @@ public class Throwables {
     public static final Throwables UNKNOWN_STRUCTURE     = register("unknown_structure", VALUE_EXCEPTION);
     public static final Throwables UNKNOWN_CRITERION     = register("unknown_criterion", VALUE_EXCEPTION);
     public static final Throwables IO_EXCEPTION          = register("io_exception", THROWN_EXCEPTION_TYPE);
-    public static final Throwables NBT_READ              = register("nbt_read_error", IO_EXCEPTION);
-    public static final Throwables JSON_READ             = register("json_read_error", IO_EXCEPTION);
+    public static final Throwables NBT_ERROR             = register("nbt_error", IO_EXCEPTION);
+    public static final Throwables JSON_ERROR            = register("json_error", IO_EXCEPTION);
     public static final Throwables USER_DEFINED          = register("user_exception", THROWN_EXCEPTION_TYPE);
 
     /**
diff --git a/src/main/java/carpet/script/language/Loops.java b/src/main/java/carpet/script/language/Loops.java
index 4908a853..ad041cd2 100644
--- a/src/main/java/carpet/script/language/Loops.java
+++ b/src/main/java/carpet/script/language/Loops.java
@@ -119,7 +119,7 @@ public class Loops {
                 c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i"));
                 try
                 {
-                    result.add(expr.evalValue(c));
+                    result.add(expr.evalValue(c, t));
                 }
                 catch (BreakStatement | ContinueStatement stmt)
                 {

From ecb578bf495ce12f5ce6a9f966d87ef39b788456 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sat, 20 Mar 2021 17:39:37 -0300
Subject: [PATCH 58/87] fixed chopped directories names.

---
 src/main/java/carpet/script/argument/FileArgument.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java
index 8fbef1c5..4b9c865e 100644
--- a/src/main/java/carpet/script/argument/FileArgument.java
+++ b/src/main/java/carpet/script/argument/FileArgument.java
@@ -258,7 +258,7 @@ public class FileArgument
             close();
         }
         if (type == Type.FOLDER)
-            return strings.map(s -> s.endsWith("/")?s.substring(0, s.length()-2):s);
+            return strings.map(s -> s.endsWith("/")?s.substring(0, s.length()-1):s);
         return strings.map(FilenameUtils::removeExtension);
     }
 

From 53f6a49b8df1977bc43e1d68f385fe7e3638a043 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sat, 20 Mar 2021 17:41:17 -0300
Subject: [PATCH 59/87] removed sun's import

---
 src/main/java/carpet/script/argument/FileArgument.java | 6 ++----
 src/main/java/carpet/script/bundled/Module.java        | 2 +-
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java
index 4b9c865e..f25176e2 100644
--- a/src/main/java/carpet/script/argument/FileArgument.java
+++ b/src/main/java/carpet/script/argument/FileArgument.java
@@ -15,7 +15,6 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonParseException;
 import com.google.gson.JsonParser;
 import com.google.gson.stream.JsonReader;
-import com.sun.nio.zipfs.ZipPath;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.NbtIo;
 import net.minecraft.nbt.PositionTracker;
@@ -368,7 +367,7 @@ public class FileArgument
             Path dataFile = toPath(module);
             if (dataFile == null) return false;
             createPaths(dataFile);
-            return writeTagDisk(tag, dataFile);
+            return writeTagDisk(tag, dataFile, zipContainer != null);
         } }
         finally
         {
@@ -377,12 +376,11 @@ public class FileArgument
     }
 
     //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
-    public static boolean writeTagDisk(Tag tag_1, Path path)
+    public static boolean writeTagDisk(Tag tag_1, Path path, boolean zipped)
     {
         Path original = path;
         try
         {
-            boolean zipped = path instanceof ZipPath;
             if (!zipped)
             {
                 path = new File(path.toFile().getAbsolutePath() + "_tmp").toPath();
diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java
index f76c4ad8..a6d29d85 100644
--- a/src/main/java/carpet/script/bundled/Module.java
+++ b/src/main/java/carpet/script/bundled/Module.java
@@ -27,7 +27,7 @@ public abstract class Module
         Path dataFile = resolveResource(module);
         if (dataFile == null) return;
         if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return;
-        synchronized (FileArgument.writeIOSync) { FileArgument.writeTagDisk(globalState, dataFile); }
+        synchronized (FileArgument.writeIOSync) { FileArgument.writeTagDisk(globalState, dataFile, false); }
     }
 
     private static Path resolveResource(Module module)

From 47f9efb0f264c45439aee94b623da99edbc7741e Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 21 Mar 2021 02:33:24 -0300
Subject: [PATCH 60/87] extended info provided with system_info

---
 docs/scarpet/api/Auxiliary.md                       |  6 ++++++
 src/main/java/carpet/script/utils/SystemInfo.java   | 13 +++++++++++++
 src/main/java/carpet/script/value/BooleanValue.java |  5 +++++
 3 files changed, 24 insertions(+)

diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md
index 952e0f15..466e9ec6 100644
--- a/docs/scarpet/api/Auxiliary.md
+++ b/docs/scarpet/api/Auxiliary.md
@@ -621,7 +621,13 @@ Available options in the scarpet app space:
   * `game_view_distance` - the view distance
   * `game_mod_name` - the name of the base mod. Expect `'fabric'`
   * `game_version` - base version of the game
+  * `game_target` - target release version
+  * `game_major_target` - major release target. For 1.12.2, that would be 12
+  * `game_minor_reease` - minor release target. For 1.12.2, that woudl be 2
+  * `game_protocol` - protocol version number
+  * `game_pack_version` - datapack version number
   * `game_data_version` - data version of the game. Returns an integer, so it can be compared.
+  * `game_stable` - indicating if its a production release or a snapshot
   
  Server related properties
  * `server_motd` - the motd of the server visible when joining
diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java
index 79dd6b1a..18c69d3e 100644
--- a/src/main/java/carpet/script/utils/SystemInfo.java
+++ b/src/main/java/carpet/script/utils/SystemInfo.java
@@ -4,6 +4,7 @@ import carpet.CarpetServer;
 import carpet.CarpetSettings;
 import carpet.script.CarpetContext;
 import carpet.script.CarpetScriptHost;
+import carpet.script.value.BooleanValue;
 import carpet.script.value.ListValue;
 import carpet.script.value.MapValue;
 import carpet.script.value.NumericValue;
@@ -59,7 +60,19 @@ public class SystemInfo {
         put("game_view_distance", c -> new NumericValue(c.s.getMinecraftServer().getPlayerManager().getViewDistance()));
         put("game_mod_name", c -> StringValue.of(c.s.getMinecraftServer().getServerModName()));
         put("game_version", c -> StringValue.of(c.s.getMinecraftServer().getVersion()));
+        put("game_target", c -> StringValue.of(SharedConstants.getGameVersion().getReleaseTarget()));
+        put("game_protocol", c -> NumericValue.of(SharedConstants.method_31372()));
+        put("game_major_target", c -> {
+            String [] vers = SharedConstants.getGameVersion().getReleaseTarget().split("\\.");
+            return NumericValue.of((vers.length > 1)?Integer.parseInt(vers[1]):0);
+        });
+        put("game_minor_target", c -> {
+            String [] vers = SharedConstants.getGameVersion().getReleaseTarget().split("\\.");
+            return NumericValue.of((vers.length > 2)?Integer.parseInt(vers[2]):0);
+        });
+        put("game_stable", c -> BooleanValue.of(SharedConstants.getGameVersion().isStable()));
         put("game_data_version", c->NumericValue.of(SharedConstants.getGameVersion().getWorldVersion()));
+        put("game_pack_version", c->NumericValue.of(SharedConstants.getGameVersion().getPackVersion()));
 
         put("server_ip", c -> StringValue.of(c.s.getMinecraftServer().getServerIp()));
         put("server_whitelisted", c -> new NumericValue(c.s.getMinecraftServer().isEnforceWhitelist()));
diff --git a/src/main/java/carpet/script/value/BooleanValue.java b/src/main/java/carpet/script/value/BooleanValue.java
index ea67f11b..19a9bc05 100644
--- a/src/main/java/carpet/script/value/BooleanValue.java
+++ b/src/main/java/carpet/script/value/BooleanValue.java
@@ -16,6 +16,11 @@ public class BooleanValue extends NumericValue
         boolValue = boolval;
     }
 
+    public static BooleanValue of(boolean value)
+    {
+        return value?TRUE:FALSE;
+    }
+
     @Override
     public String getString() {
         return boolValue?"true":"false";

From 02e097dc4fd9f0b2ca51e9c74fef8aa4ec9c0fb6 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 23 Mar 2021 04:15:51 -0300
Subject: [PATCH 61/87] fix

---
 src/main/java/carpet/script/value/EntityValue.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 5172c4d9..3b90cdeb 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -1307,7 +1307,7 @@ public class EntityValue extends Value
             else if (v instanceof ListValue)
             {
                 List lv = ((ListValue) v).getItems();
-                if (lv.size() >= 1 && lv.size() <= 5)
+                if (lv.size() >= 1 && lv.size() <= 6)
                 {
                     String effectName = lv.get(0).getString();
                     StatusEffect effect = Registry.STATUS_EFFECT.get(new Identifier(effectName));

From 1ef764b9b132c393418212f3d464c4a18211da99 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 25 Mar 2021 14:03:09 -0300
Subject: [PATCH 62/87] specify

---
 docs/scarpet/api/BlocksAndWorldAccess.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index bcde5013..ecfe0cea 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -325,6 +325,8 @@ With `column_search` set to `true`, it will return all POIs in a cuboid with `ra
 block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius`
 radius. 
 
+All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`.
+
 The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates.
 
 Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs 

From c5f666b95c0f4040883d40871eccfcacd2f7f3e6 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 26 Mar 2021 22:29:06 -0300
Subject: [PATCH 63/87] specify

---
 docs/scarpet/api/Entities.md                       |  4 ++++
 src/main/java/carpet/script/value/EntityValue.java | 14 ++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md
index 5e013034..20ffa81d 100644
--- a/docs/scarpet/api/Entities.md
+++ b/docs/scarpet/api/Entities.md
@@ -722,6 +722,10 @@ Requires a living entity as an argument.
 
 Will make the entity jump once.
 
+### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')`
+
+Makes the living entity swing their required limb.
+
 ### `modify(e, 'silent', boolean)`
 
 Silences or unsilences the entity.
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 3b90cdeb..110a5ce4 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -56,6 +56,7 @@ import net.minecraft.server.command.ServerCommandSource;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.text.LiteralText;
+import net.minecraft.util.Hand;
 import net.minecraft.util.Identifier;
 import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.hit.EntityHitResult;
@@ -1376,6 +1377,19 @@ public class EntityValue extends Value
             }
         });
 
+        put("swing", (e, v) -> {
+            if (e instanceof LivingEntity)
+            {
+                Hand hand = Hand.MAIN_HAND;
+                if (v != null)
+                {
+                    String handString = v.getString().toLowerCase(Locale.ROOT);
+                    if (handString.equals("offhand") || handString.equals("off_hand")) hand = Hand.OFF_HAND;
+                }
+                ((LivingEntity)e).swingHand(hand, true);
+            }
+        });
+
         put("silent",(e,v)-> e.setSilent(v.getBoolean()));
 
         put("gravity",(e,v)-> e.setNoGravity(!v.getBoolean()));

From 4ad1c3e029004a08eb7f3bbb02438d88aa4824f0 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Wed, 31 Mar 2021 22:19:33 +0000
Subject: [PATCH 64/87] Merge docs for 'Carpet Mod 1.4.30 for Minecraft 1.16.5,
 21w13a'

---
 docs/scarpet/Full.md | 62 ++++++++++++++++++++++++++++++++++----------
 1 file changed, 49 insertions(+), 13 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index eed59c12..bcb529e5 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -922,6 +922,11 @@ map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true,
 map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
 
+## `reset_seed(seed)` + +Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value +indicating if the given seed has been used or not. + ### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)` returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples @@ -1508,8 +1513,8 @@ but like everywhere else, doing that sounds like a bad idea. `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific errors thrown when a specified internal name does not exist or is invalid. - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. - - `nbt_read_error`: Incorrect input NBT file. - - `json_read_error`: Incorrect input JSON data. + - `nbt_error`: Incorrect input/output NBT file. + - `json_error`: Incorrect input/output JSON data. - `user_exception`: Exception thrown by default with `throw` function. Synopsis: @@ -2431,16 +2436,18 @@ 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, ... }'
 
-### `poi(pos), poi(pos, radius?, type?, status?, column?)` +### `poi(pos), poi(pos, radius?, type?, status?, column_search?)` Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`. If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status. -With `column` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire +With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius` radius. +All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`. + The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates. Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs @@ -3708,6 +3715,10 @@ Requires a living entity as an argument. Will make the entity jump once. +### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')` + +Makes the living entity swing their required limb. + ### `modify(e, 'silent', boolean)` Silences or unsilences the entity. @@ -4438,13 +4449,12 @@ Displays or modifies individual scoreboard values. With no arguments, returns th With specified `objective`, lists all keys (players) associated with current objective, or `null` if objective does not exist. With specified `objective` and `key`, returns current value of the objective for a given player (key). With additional `value` sets a new scoreboard - value, returning previous value associated with the `key`. + value, returning previous value associated with the `key`. If the `value` is null, resets the scoreboard value. ### `scoreboard_add(objective, criterion?)` Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. -If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. -If the objective was added, returns `true`. If nothing is affected, returns `null` +Returns `true` if the objective was created, or `null` if an objective with the specified name already exists. Throws `unknown_criterion` if criterion doesn't exist. @@ -4465,6 +4475,15 @@ for the objective. Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, returns `null`. +### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)` + +Reads a property of an `objective` or sets it to a `value` if specified. Available properties are: + +* `criterion` +* `display_name` (Formatted text supported) +* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot +* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified + # Team ### `team_list()`, `team_list(team)` @@ -4603,7 +4622,7 @@ Collection of other methods that control smaller, yet still important aspects of ## Sounds -### `sound(name, pos, volume?, pitch?, mixer?)` +### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)` Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. @@ -4611,17 +4630,23 @@ Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostil and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. +Used with no arguments, return the list of available sound names. + Throws `unknown_sound` if sound doesn't exist. ## Particles -### `particle(name, pos, count?. spread?, speed?, player?)` +### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)` Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed` of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle` command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. +Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid +particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration +to be valid, like `dust`, `block` etc. Should be used as a reference only. + Throws `unknown_particle` if particle doesn't exist. ### `particle_line(name, pos, pos2, density?, player?)` @@ -4673,7 +4698,7 @@ Optional shared shape attributes: in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`. * `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified player, otherwise it will be send to all players in the dimension. - * `line` - line thickness, defaults to 2.0pt + * `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer. * `fill` - color for the faces, defaults to no fill. Use `color` attribute format * `follow` - entity, or player name. Shape will follow an entity instead of being static. Follow attribute requires all positional arguments to be relative to the entity and disallow @@ -4707,7 +4732,7 @@ Available shapes: * `align` - text alignment with regards to `pos`. Default is `center` (displayed text is centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos` indicates the end of text). - * `tilt` - additional rotation of the text on the canvas + * `tilt`, `lean`, `turn` - additional rotations of the text on the canvas along all three axis * `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`) with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which can be used to display multiple lines of text bound to the same `pos` @@ -4891,7 +4916,8 @@ Resource is identified by a path to the file. A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. Do not add file extensions to the descriptor - extensions are inferred -based on the `type` of the file. +based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from +zip files, although you cannot nest zip files in other zip files. Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning @@ -4905,7 +4931,7 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s The default no-name app, via `/script run` command can only save/load/read files from the shared space. -Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true` +Functions return `null` if no file is present (for read, list and delete operations). Returns `true` for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files for folder listing. @@ -4929,6 +4955,10 @@ third argument. Throws: - `nbt_read_error`: When failed to read NBT file. - `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +- `io_exception`: For all other errors when handling data on disk not related to encoding issues + +All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception +thrown.
 write_file('foo', 'shared_text, ['one', 'two']);
@@ -5209,7 +5239,13 @@ Available options in the scarpet app space:
   * `game_view_distance` - the view distance
   * `game_mod_name` - the name of the base mod. Expect `'fabric'`
   * `game_version` - base version of the game
+  * `game_target` - target release version
+  * `game_major_target` - major release target. For 1.12.2, that would be 12
+  * `game_minor_reease` - minor release target. For 1.12.2, that woudl be 2
+  * `game_protocol` - protocol version number
+  * `game_pack_version` - datapack version number
   * `game_data_version` - data version of the game. Returns an integer, so it can be compared.
+  * `game_stable` - indicating if its a production release or a snapshot
   
  Server related properties
  * `server_motd` - the motd of the server visible when joining

From 470821524d9980ab28d481e0421938cf9a763473 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Wed, 31 Mar 2021 22:03:09 -0300
Subject: [PATCH 65/87] 1.4.30 foreal

---
 src/main/java/carpet/CarpetSettings.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index b3ef771c..20b241e9 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT;
 @SuppressWarnings("CanBeFinal")
 public class CarpetSettings
 {
-    public static final String carpetVersion = "1.4.30+v210317";
+    public static final String carpetVersion = "1.4.30+v210331";
     public static final Logger LOG = LogManager.getLogger("carpet");
     public static boolean skipGenerationChecks = false;
     public static boolean impendingFillSkipUpdates = false;

From 5c7bbc19adaf7ad957aae5c00c24341359bddf30 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sat, 3 Apr 2021 13:16:39 -0300
Subject: [PATCH 66/87] optional mixins to provide better compatibility. fixes
 #788

---
 .../java/carpet/mixins/SoundEngineInt_cleanLogsMixin.java     | 4 ++--
 src/main/java/carpet/mixins/SoundSystem_cleanLogsMixin.java   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/carpet/mixins/SoundEngineInt_cleanLogsMixin.java b/src/main/java/carpet/mixins/SoundEngineInt_cleanLogsMixin.java
index 559c935c..c6316280 100644
--- a/src/main/java/carpet/mixins/SoundEngineInt_cleanLogsMixin.java
+++ b/src/main/java/carpet/mixins/SoundEngineInt_cleanLogsMixin.java
@@ -6,11 +6,11 @@ import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Redirect;
 
-@Mixin(targets="net.minecraft.client.sound.SoundEngine$SourceSetImpl")
+@Mixin(targets="net.minecraft.client.sound.SoundEngine$SourceSetImpl", priority = 69420)
 public class SoundEngineInt_cleanLogsMixin
 {
     // createSource //  method_19666, remap = false,
-    @Redirect(method = "createSource()Lnet/minecraft/client/sound/Source;", at = @At(remap = false,
+    @Redirect(method = "createSource()Lnet/minecraft/client/sound/Source;", require = 0, at = @At(remap = false,
             value="INVOKE",
             target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"
     ))
diff --git a/src/main/java/carpet/mixins/SoundSystem_cleanLogsMixin.java b/src/main/java/carpet/mixins/SoundSystem_cleanLogsMixin.java
index 5ef391d7..19a7287f 100644
--- a/src/main/java/carpet/mixins/SoundSystem_cleanLogsMixin.java
+++ b/src/main/java/carpet/mixins/SoundSystem_cleanLogsMixin.java
@@ -7,10 +7,10 @@ import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Redirect;
 
-@Mixin(SoundSystem.class)
+@Mixin(value = SoundSystem.class, priority = 69420)
 public class SoundSystem_cleanLogsMixin
 {
-    @Redirect(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", at = @At( remap = false,
+    @Redirect(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", require = 0, at = @At( remap = false,
             value = "INVOKE",
             target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;)V"
     ))

From 3f372180e60458860e35d597a35799230ccb7175 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 12:33:52 -0300
Subject: [PATCH 67/87] increased store for random seeds to 64k

---
 docs/scarpet/language/SystemFunctions.md    | 11 ++++++-----
 src/main/java/carpet/script/ScriptHost.java |  2 +-
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md
index eb453bcd..1ddcc8e9 100644
--- a/docs/scarpet/language/SystemFunctions.md
+++ b/docs/scarpet/language/SystemFunctions.md
@@ -227,11 +227,12 @@ length('foo') => 3
 ### `rand(expr), rand(expr, seed)`
 
 returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, 
-boolean functions, or `bool`), returns false if the randomly selected value is less than 1\. This means 
-that `rand(2)` returns true half of the time and `rand(5)` returns true for 80% (4/5) of the time. If seed is not 
-provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the 
-same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number 
-(per app), then your random sequence will revert to the beginning.
+boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means 
+that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not 
+provided, uses a random seed that's shared across all scarpet apps. 
+If seed is provided, each consecutive call to rand() will act like 'next' call to the 
+same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app), 
+so if you exceed this number, your random sequences will revert to the beginning and start over.
 
 
 map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java
index 255f76d4..f57ae663 100644
--- a/src/main/java/carpet/script/ScriptHost.java
+++ b/src/main/java/carpet/script/ScriptHost.java
@@ -37,7 +37,7 @@ public abstract class ScriptHost
 
     public Random getRandom(long aLong)
     {
-        if (randomizers.size() > 1024)
+        if (randomizers.size() > 65536)
             randomizers.clear();
         return randomizers.computeIfAbsent(aLong, Random::new);
     }

From 27612905b8e81006d5924a301241e5770b3bd386 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 17:54:05 -0300
Subject: [PATCH 68/87] added system_info world_spawn_point. Fixes #785

---
 src/main/java/carpet/script/utils/SystemInfo.java | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java
index 18c69d3e..36d00396 100644
--- a/src/main/java/carpet/script/utils/SystemInfo.java
+++ b/src/main/java/carpet/script/utils/SystemInfo.java
@@ -18,6 +18,7 @@ import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.SharedConstants;
 import net.minecraft.util.WorldSavePath;
 import net.minecraft.world.GameRules;
+import net.minecraft.world.WorldProperties;
 
 import java.lang.management.ManagementFactory;
 import java.nio.file.Path;
@@ -52,6 +53,11 @@ public class SystemInfo {
             return StringValue.of(tlf);
         });
         put("world_dimensions", c -> ListValue.wrap(c.s.getMinecraftServer().getWorldRegistryKeys().stream().map(k -> ValueConversions.of(k.getValue())).collect(Collectors.toList())));
+        put("world_spawn_point", c -> {
+            WorldProperties prop = c.s.getMinecraftServer().getOverworld().getLevelProperties();
+            return ListValue.of(NumericValue.of(prop.getSpawnX()), NumericValue.of(prop.getSpawnY()), NumericValue.of(prop.getSpawnZ()));
+        });
+
         put("game_difficulty", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getDifficulty().getName()));
         put("game_hardcore", c -> new NumericValue(c.s.getMinecraftServer().getSaveProperties().isHardcore()));
         put("game_storage_format", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getFormatName(c.s.getMinecraftServer().getSaveProperties().getVersion())));

From 8f5330e241f1c23d272384f336e362fdbb9ca1c7 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 17:55:57 -0300
Subject: [PATCH 69/87] added system_info world_spawn_point. Fixes #785

---
 docs/scarpet/api/Auxiliary.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md
index 466e9ec6..0b4fad16 100644
--- a/docs/scarpet/api/Auxiliary.md
+++ b/docs/scarpet/api/Auxiliary.md
@@ -610,7 +610,7 @@ Available options in the scarpet app space:
   * `world_folder` - name of the direct folder in the saves that holds world files
   * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
   * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
-
+  * `world_spawn_point` - world spawn point
 
  Relevant gameplay related properties
   * `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`

From 00f932bafcbedf2e3348568b5272e258df058596 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 18:28:25 -0300
Subject: [PATCH 70/87] Can place skulls inside when creativeNoClip. Fixes #775

---
 ...StandingBlockItem_creativeNoClipMixin.java | 38 +++++++++++++++++++
 src/main/resources/carpet.mixins.json         |  1 +
 2 files changed, 39 insertions(+)
 create mode 100644 src/main/java/carpet/mixins/WallStandingBlockItem_creativeNoClipMixin.java

diff --git a/src/main/java/carpet/mixins/WallStandingBlockItem_creativeNoClipMixin.java b/src/main/java/carpet/mixins/WallStandingBlockItem_creativeNoClipMixin.java
new file mode 100644
index 00000000..b8035233
--- /dev/null
+++ b/src/main/java/carpet/mixins/WallStandingBlockItem_creativeNoClipMixin.java
@@ -0,0 +1,38 @@
+package carpet.mixins;
+
+import carpet.CarpetSettings;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.ShapeContext;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemPlacementContext;
+import net.minecraft.item.WallStandingBlockItem;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.shape.VoxelShape;
+import net.minecraft.world.WorldView;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+@Mixin(WallStandingBlockItem.class)
+public class WallStandingBlockItem_creativeNoClipMixin
+{
+    @Redirect(method = "getPlacementState", at = @At(
+                value = "INVOKE",
+                target = "Lnet/minecraft/world/WorldView;canPlace(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/ShapeContext;)Z"
+    ))
+    private boolean canCreativePlayerPlace(
+            WorldView worldView, BlockState state, BlockPos pos, ShapeContext context,
+            ItemPlacementContext itemcontext
+    )
+    {
+        PlayerEntity player = itemcontext.getPlayer();
+        if (CarpetSettings.creativeNoClip && player != null && player.isCreative() && player.abilities.flying)
+        {
+            // copy from canPlace
+            VoxelShape voxelShape = state.getCollisionShape(worldView, pos, context);
+            return voxelShape.isEmpty() || worldView.intersectsEntities(player, voxelShape.offset(pos.getX(), pos.getY(), pos.getZ()));
+
+        }
+        return worldView.canPlace(state, pos, context);
+    }
+}
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index 1662b4f9..f945eb81 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -194,6 +194,7 @@
     "PlayerEntity_creativeNoClipMixin",
     "PistonBlockEntity_playerHandlingMixin",
     "BlockItem_creativeNoClipMixin",
+    "WallStandingBlockItem_creativeNoClipMixin",
     "LivingEntity_creativeFlyMixin"
 
   ],

From dafebffb5af93c25b23aa228020c4fb3ee7e6afd Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 19:22:06 -0300
Subject: [PATCH 71/87] Rewording since all extra semicolons are now stripped
 in tokenization stage. Fixes #771

---
 src/main/java/carpet/script/Expression.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index 7aa7ec0f..33c1d1ee 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -765,7 +765,7 @@ public class Expression
                     {
                         if (token.surface.equals(";"))
                         {
-                            throw new ExpressionException(c, this, token, "Unnecessary semicolon");
+                            throw new ExpressionException(c, this, token, "Empty expression found with ';'");
                         }
                         throw new ExpressionException(c, this, token, "Missing parameter(s) for operator " + token);
                     }

From 9117d85c7303281087fb5f88e02d06479b198b95 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 19:45:08 -0300
Subject: [PATCH 72/87] for

---
 src/main/java/carpet/script/Expression.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index 33c1d1ee..d0c36074 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -765,7 +765,7 @@ public class Expression
                     {
                         if (token.surface.equals(";"))
                         {
-                            throw new ExpressionException(c, this, token, "Empty expression found with ';'");
+                            throw new ExpressionException(c, this, token, "Empty expression found for ';'");
                         }
                         throw new ExpressionException(c, this, token, "Missing parameter(s) for operator " + token);
                     }

From 51a548991de968f31804e7aeb5fd9ced5594d309 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 20:56:50 -0300
Subject: [PATCH 73/87] reword to properly reflect what up. fixes #761

---
 src/main/java/carpet/commands/PerimeterInfoCommand.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/commands/PerimeterInfoCommand.java b/src/main/java/carpet/commands/PerimeterInfoCommand.java
index 901ea10e..975ac231 100644
--- a/src/main/java/carpet/commands/PerimeterInfoCommand.java
+++ b/src/main/java/carpet/commands/PerimeterInfoCommand.java
@@ -57,7 +57,7 @@ public class PerimeterInfoCommand
             });
             if (!(baseEntity instanceof  MobEntity))
             {
-                Messenger.m(source, "r Failed to spawn test entity");
+                Messenger.m(source, "r /perimeterinfo requires a mob entity to test agains.");
                 if (baseEntity != null) baseEntity.remove();
                 return 0;
             }

From f0d044a49f5714a6d5c0f5ba66b9ee181c17ceec Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 22:32:34 -0300
Subject: [PATCH 74/87] added scarpet to the carpet profiler

---
 src/main/java/carpet/commands/ScriptCommand.java |  6 +++++-
 .../java/carpet/script/CarpetEventServer.java    |  3 +++
 .../java/carpet/script/CarpetScriptHost.java     | 16 +++++++++++++---
 .../java/carpet/script/CarpetScriptServer.java   | 13 ++++++++-----
 .../carpet/script/command/CommandArgument.java   |  6 +++++-
 src/main/java/carpet/utils/CarpetProfiler.java   |  4 +++-
 6 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java
index 75030545..83449f9f 100644
--- a/src/main/java/carpet/commands/ScriptCommand.java
+++ b/src/main/java/carpet/commands/ScriptCommand.java
@@ -13,6 +13,7 @@ import carpet.script.exception.CarpetExpressionException;
 import carpet.script.value.FunctionValue;
 import carpet.script.value.Value;
 import carpet.settings.SettingsManager;
+import carpet.utils.CarpetProfiler;
 import carpet.utils.Messenger;
 import com.mojang.brigadier.CommandDispatcher;
 import com.mojang.brigadier.arguments.StringArgumentType;
@@ -396,6 +397,7 @@ public class ScriptCommand
     {
         try
         {
+            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet ace", CarpetProfiler.TYPE.GENERAL);
             host.setChatErrorSnooper(source);
             long start = System.nanoTime();
             Value result = call.get();
@@ -412,7 +414,9 @@ public class ScriptCommand
                 metric = "s";
             }
             Messenger.m(source, "wi  = ", "wb "+result.getString(), "gi  ("+time+metric+")");
-            return (int)result.readInteger();
+            int intres = (int)result.readInteger();
+            CarpetProfiler.end_current_section(currentSection);
+            return intres;
         }
         catch (CarpetExpressionException e)
         {
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index f017f574..3c1c084d 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -14,6 +14,7 @@ import carpet.script.value.StringValue;
 import carpet.script.value.Value;
 import carpet.script.value.ValueConversions;
 import carpet.settings.ParsedRule;
+import carpet.utils.CarpetProfiler;
 import carpet.utils.Messenger;
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
 import net.minecraft.block.BlockState;
@@ -204,6 +205,7 @@ public class CarpetEventServer
         {
             if (callList.size() > 0)
             {
+                CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet events", CarpetProfiler.TYPE.GENERAL);
                 List argv = argumentSupplier.get(); // empty for onTickDone
                 ServerCommandSource source;
                 try
@@ -225,6 +227,7 @@ public class CarpetEventServer
                     if (!call.execute(source, argv)) fails.add(call);
                 }
                 for (Callback call : fails) callList.remove(call);
+                CarpetProfiler.end_current_section(currentSection);
             }
         }
 
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index b6dc0e85..223521da 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -18,6 +18,7 @@ import carpet.script.value.MapValue;
 import carpet.script.value.NumericValue;
 import carpet.script.value.StringValue;
 import carpet.script.value.Value;
+import carpet.utils.CarpetProfiler;
 import carpet.utils.Messenger;
 
 import com.google.gson.JsonElement;
@@ -127,6 +128,7 @@ public class CarpetScriptHost extends ScriptHost
 
     private static int execute(CommandContext ctx, String hostName, FunctionArgument funcSpec, List paramNames) throws CommandSyntaxException
     {
+        CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
         CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
         List argNames = funcSpec.function.getArguments();
         if ((argNames.size()-funcSpec.args.size()) != paramNames.size())
@@ -140,7 +142,9 @@ public class CarpetScriptHost extends ScriptHost
         Value response = cHost.handleCommand(ctx.getSource(), funcSpec.function, args);
         // will skip prints for new
         //if (!response.isNull()) Messenger.m(ctx.getSource(), "gi " + response.getString());
-        return (int) response.readInteger();
+        int intres = (int) response.readInteger();
+        CarpetProfiler.end_current_section(currentSection);
+        return intres;
     }
 
     public LiteralArgumentBuilder addPathToCommand(
@@ -210,12 +214,15 @@ public class CarpetScriptHost extends ScriptHost
         return s -> {
             try
             {
+                CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
                 CarpetScriptHost cHost = null;
                 cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(s);
                 Value response = cHost.handleCommand(s, fun, Collections.singletonList(
                         (s.getEntity() instanceof ServerPlayerEntity)?new EntityValue(s.getEntity()):Value.NULL)
                 );
-                return response.getBoolean();
+                boolean res = response.getBoolean();
+                CarpetProfiler.end_current_section(currentSection);
+                return res;
             }
             catch (CommandSyntaxException e)
             {
@@ -546,7 +553,10 @@ public class CarpetScriptHost extends ScriptHost
     {
         try
         {
-            return callLegacy(source, call, coords, arg);
+            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet legacy", CarpetProfiler.TYPE.GENERAL);
+            Value res = callLegacy(source, call, coords, arg);
+            CarpetProfiler.end_current_section(currentSection);
+            return res;
         }
         catch (CarpetExpressionException exc)
         {
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index d8dfbd35..b74ef32b 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -1,19 +1,15 @@
 package carpet.script;
 
 import carpet.CarpetSettings;
-import carpet.script.argument.FunctionArgument;
 import carpet.script.bundled.BundledModule;
 import carpet.CarpetServer;
 import carpet.script.bundled.FileModule;
 import carpet.script.bundled.Module;
-import carpet.script.command.CommandToken;
 import carpet.script.exception.InvalidCallbackException;
 import carpet.script.value.FunctionValue;
-import carpet.script.value.StringValue;
 import carpet.script.value.Value;
+import carpet.utils.CarpetProfiler;
 import carpet.utils.Messenger;
-import com.mojang.brigadier.arguments.StringArgumentType;
-import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
 import com.mojang.brigadier.tree.CommandNode;
 
@@ -229,6 +225,7 @@ public class CarpetScriptServer
     public boolean addScriptHost(ServerCommandSource source, String name, Function commandValidator,
                                  boolean perPlayer, boolean autoload, boolean isRuleApp)
     {
+        CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet loading", CarpetProfiler.TYPE.GENERAL);
         if (commandValidator == null) commandValidator = p -> true;
         long start = System.nanoTime();
         name = name.toLowerCase(Locale.ROOT);
@@ -301,6 +298,7 @@ public class CarpetScriptServer
             FunctionValue onStart = newHost.getFunction("__on_start");
             if (onStart != null) newHost.callNow(onStart, Collections.emptyList());
         }
+        CarpetProfiler.end_current_section(currentSection);
         long end = System.nanoTime();
         CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms");
         return true;
@@ -412,11 +410,16 @@ public class CarpetScriptServer
 
     public void tick()
     {
+        CarpetProfiler.ProfilerToken token;
+        token = CarpetProfiler.start_section(null, "Scarpet schedule", CarpetProfiler.TYPE.GENERAL);
         events.tick();
+        CarpetProfiler.end_current_section(token);
+        token = CarpetProfiler.start_section(null, "Scarpet app data", CarpetProfiler.TYPE.GENERAL);
         for (CarpetScriptHost host : modules.values())
         {
             host.tick();
         }
+        CarpetProfiler.end_current_section(token);
     }
 
     public void onClose()
diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java
index b2e02272..f3d58934 100644
--- a/src/main/java/carpet/script/command/CommandArgument.java
+++ b/src/main/java/carpet/script/command/CommandArgument.java
@@ -14,6 +14,7 @@ import carpet.script.value.NumericValue;
 import carpet.script.value.StringValue;
 import carpet.script.value.Value;
 import carpet.script.value.ValueConversions;
+import carpet.utils.CarpetProfiler;
 import com.google.common.collect.Lists;
 import com.mojang.authlib.GameProfile;
 import com.mojang.brigadier.arguments.ArgumentType;
@@ -365,6 +366,7 @@ public abstract class CommandArgument
     {
         if (customSuggester != null)
         {
+            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
             Map params = new HashMap<>();
             for(ParsedCommandNode pnode : context.getNodes())
             {
@@ -379,7 +381,9 @@ public abstract class CommandArgument
             args.addAll(customSuggester.args);
             Value response = host.handleCommand(context.getSource(), customSuggester.function, args);
             if (!(response instanceof ListValue)) throw error("Custom suggester should return a list of options"+" for custom type "+suffix);
-            return ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList());
+            Collection res = ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList());
+            CarpetProfiler.end_current_section(currentSection);
+            return res;
         }
         if (needsMatching) return examples;
         //return Lists.newArrayList("");
diff --git a/src/main/java/carpet/utils/CarpetProfiler.java b/src/main/java/carpet/utils/CarpetProfiler.java
index 01a51d1f..53ba857e 100644
--- a/src/main/java/carpet/utils/CarpetProfiler.java
+++ b/src/main/java/carpet/utils/CarpetProfiler.java
@@ -31,7 +31,9 @@ public class CarpetProfiler
     private static int tick_health_elapsed = 0;
     private static TYPE test_type = TYPE.NONE; //1 for ticks, 2 for entities;
     private static long current_tick_start = 0;
-    private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks",};
+    private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks",
+            "Scarpet ace", "Scarpet legacy", "Scarpet events", "Scarpet schedule",
+            "Scarpet command", "Scarpet loading", "Scarpet app data"};
     private static final String[] SECTIONS = {
             "Spawning and Random Ticks", "Ticket Manager","Unloading",
             "Blocks", "Entities", "Block Entities",

From ebd0f7879402e4d70686c22e790a3285aeb42798 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 4 Apr 2021 22:58:26 -0300
Subject: [PATCH 75/87] fix npe

---
 src/main/java/carpet/utils/CarpetProfiler.java | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/main/java/carpet/utils/CarpetProfiler.java b/src/main/java/carpet/utils/CarpetProfiler.java
index 53ba857e..0ca76092 100644
--- a/src/main/java/carpet/utils/CarpetProfiler.java
+++ b/src/main/java/carpet/utils/CarpetProfiler.java
@@ -149,12 +149,7 @@ public class CarpetProfiler
         {
             String current_section = (tok.world == null) ? (String) tok.section :
                     getSectionString(tok.world, (String)tok.section);
-            if (SECTION_STATS.get(current_section) == null)
-            {
-                CarpetSettings.LOG.error("Current section: "+current_section);
-            }
-            else
-                SECTION_STATS.put(current_section, SECTION_STATS.get(current_section) + end_time - tok.start);
+            SECTION_STATS.put(current_section, SECTION_STATS.getOrDefault(current_section, 0L) + end_time - tok.start);
         }
     }
 
@@ -236,7 +231,8 @@ public class CarpetProfiler
             boolean hasSomethin = false;
             for (String section : SECTIONS)
             {
-                double amount = divider * SECTION_STATS.get(dimension + "." + section);
+
+                double amount = divider * SECTION_STATS.getOrDefault(dimension + "." + section, 0L);
                 if (amount > 0.01)
                 {
                     hasSomethin = true;
@@ -250,7 +246,7 @@ public class CarpetProfiler
             Messenger.m(currentRequester, "gi "+dimension + ":");
             for (String section : SECTIONS)
             {
-                double amount = divider * SECTION_STATS.get(dimension + "." + section);
+                double amount = divider * SECTION_STATS.getOrDefault(dimension + "." + section, 0L);
                 if (amount > 0.01)
                 {
                     if (!(section.endsWith("(Client)")))

From 6f4ee1fd00fcb58abd5e769215ef7d3d46b939f3 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Mon, 5 Apr 2021 09:33:04 -0300
Subject: [PATCH 76/87] fake players shake off players when removing

---
 .../java/carpet/patches/EntityPlayerMPFake.java     | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/src/main/java/carpet/patches/EntityPlayerMPFake.java b/src/main/java/carpet/patches/EntityPlayerMPFake.java
index e7932250..3f7afd46 100644
--- a/src/main/java/carpet/patches/EntityPlayerMPFake.java
+++ b/src/main/java/carpet/patches/EntityPlayerMPFake.java
@@ -2,6 +2,8 @@ package carpet.patches;
 
 import com.mojang.authlib.GameProfile;
 import net.minecraft.block.entity.SkullBlockEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.ItemStack;
 import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket;
@@ -102,6 +104,7 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
 
     public void kill(Text reason)
     {
+        shakeOff();
         this.server.send(new ServerTask(this.server.getTicks(), () -> {
             this.networkHandler.onDisconnected(reason);
         }));
@@ -120,9 +123,19 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
         this.playerTick();
     }
 
+    private void shakeOff()
+    {
+        if (getVehicle() instanceof PlayerEntity) stopRiding();
+        for (Entity passenger : getPassengersDeep())
+        {
+            if (passenger instanceof PlayerEntity) passenger.stopRiding();
+        }
+    }
+
     @Override
     public void onDeath(DamageSource cause)
     {
+        shakeOff();
         super.onDeath(cause);
         setHealth(20);
         this.hungerManager = new HungerManager();

From e37e4050b13d1d127dc978cce13ea59c0202a319 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Mon, 5 Apr 2021 10:58:26 -0300
Subject: [PATCH 77/87] get proper lazy host for execution of suggestions.
 Fixes #793

---
 src/main/java/carpet/script/command/CommandArgument.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java
index f3d58934..1285a838 100644
--- a/src/main/java/carpet/script/command/CommandArgument.java
+++ b/src/main/java/carpet/script/command/CommandArgument.java
@@ -1,5 +1,6 @@
 package carpet.script.command;
 
+import carpet.CarpetServer;
 import carpet.fakes.BlockStateArgumentInterface;
 import carpet.script.CarpetScriptHost;
 import carpet.script.argument.FunctionArgument;
@@ -277,7 +278,12 @@ public abstract class CommandArgument
     {
         CommandArgument arg = getTypeForArgument(param, host);
         if (arg.suggestionProvider != null) return argument(param, arg.getArgumentType()).suggests(arg.suggestionProvider);
-        return arg.needsMatching? argument(param, arg.getArgumentType()).suggests((c, b) -> arg.suggest(c, b, host)) : argument(param, arg.getArgumentType());
+        if (!arg.needsMatching) return argument(param, arg.getArgumentType());
+        String hostName = host.getName();
+        return argument(param, arg.getArgumentType()).suggests((ctx, b) -> {
+            CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
+            return arg.suggest(ctx, b, cHost);
+        });
     }
 
     protected String suffix;

From 063b4b84ec88f27494345dcb8c615817a98cc803 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Mon, 5 Apr 2021 13:42:22 -0300
Subject: [PATCH 78/87] deferred creation of a dummy entity for block breaking
 to when its needed.

---
 src/main/java/carpet/script/api/WorldAccess.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index 46b1cbbe..1d81c5b5 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -121,8 +121,7 @@ public class WorldAccess {
         put("unknown", ChunkTicketType.UNKNOWN);  // unknown
     }};
     // dummy entity for dummy requirements in the loot tables (see snowball)
-    private static FallingBlockEntity DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null);
-
+    private static FallingBlockEntity DUMMY_ENTITY = null;
     private static LazyValue booleanStateTest(
             Context c,
             String name,
@@ -930,6 +929,7 @@ public class WorldAccess {
                 {
                     if (how > 0)
                         tool.addEnchantment(Enchantments.FORTUNE, (int) how);
+                    if (DUMMY_ENTITY == null) DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null);
                     Block.dropStacks(state, world, where, be, DUMMY_ENTITY, tool);
                 }
             }

From 647a3a15b6cfa73ee9f24c90569561cb374398bf Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 6 Apr 2021 00:41:12 -0300
Subject: [PATCH 79/87] profiler rework

---
 .../java/carpet/commands/ScriptCommand.java   |   2 +-
 .../mixins/MinecraftServer_coreMixin.java     |   3 +
 .../MinecraftServer_tickspeedMixin.java       |   3 +-
 .../carpet/network/ClientNetworkHandler.java  |   2 +-
 .../java/carpet/script/CarpetScriptHost.java  |   2 +-
 .../carpet/script/CarpetScriptServer.java     |   2 +-
 .../carpet/script/utils/ShapesRenderer.java   |   7 ++
 .../java/carpet/utils/CarpetProfiler.java     | 114 +++++++++---------
 8 files changed, 76 insertions(+), 59 deletions(-)

diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java
index 83449f9f..bb83e056 100644
--- a/src/main/java/carpet/commands/ScriptCommand.java
+++ b/src/main/java/carpet/commands/ScriptCommand.java
@@ -397,7 +397,7 @@ public class ScriptCommand
     {
         try
         {
-            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet ace", CarpetProfiler.TYPE.GENERAL);
+            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet run", CarpetProfiler.TYPE.GENERAL);
             host.setChatErrorSnooper(source);
             long start = System.nanoTime();
             Value result = call.get();
diff --git a/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java b/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
index 0638d9b9..8b47e629 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
@@ -2,6 +2,7 @@ package carpet.mixins;
 
 import carpet.CarpetServer;
 import carpet.CarpetSettings;
+import carpet.utils.CarpetProfiler;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.WorldGenerationProgressListener;
 import org.spongepowered.asm.mixin.Mixin;
@@ -26,7 +27,9 @@ public abstract class MinecraftServer_coreMixin
             )
     )
     private void onTick(BooleanSupplier booleanSupplier_1, CallbackInfo ci) {
+        CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Carpet", CarpetProfiler.TYPE.GENERAL);
         CarpetServer.tick((MinecraftServer) (Object) this);
+        CarpetProfiler.end_current_section(token);
     }
 
     @Inject(method = "loadWorld", at = @At("HEAD"))
diff --git a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
index 03bb1f38..6f11ef08 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
@@ -208,7 +208,8 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
     {
         if (CarpetProfiler.tick_health_requested != 0L)
         {
-            CarpetProfiler.end_tick_profiling((MinecraftServer) (Object)this, currentSection);
+            CarpetProfiler.end_current_section(currentSection);
+            CarpetProfiler.end_tick_profiling((MinecraftServer) (Object)this);
         }
     }
 
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index 0d60acc0..477c556b 100644
--- a/src/main/java/carpet/network/ClientNetworkHandler.java
+++ b/src/main/java/carpet/network/ClientNetworkHandler.java
@@ -74,7 +74,7 @@ public class ClientNetworkHandler
             TickSpeed.is_superHot = ((ByteTag) t).equals(ByteTag.ONE);
         });
         dataHandlers.put("TickPlayerActiveTimeout", (p, t) -> TickSpeed.player_active_timeout = ((AbstractNumberTag)t).getInt());
-        dataHandlers.put("scShape", (p, t) -> { // deprecated
+        dataHandlers.put("scShape", (p, t) -> { // deprecated // and unused // should remove for 1.17
             if (CarpetClient.shapes != null)
                 CarpetClient.shapes.addShape((CompoundTag)t);
         });
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index 223521da..36bb7b23 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -553,7 +553,7 @@ public class CarpetScriptHost extends ScriptHost
     {
         try
         {
-            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet legacy", CarpetProfiler.TYPE.GENERAL);
+            CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
             Value res = callLegacy(source, call, coords, arg);
             CarpetProfiler.end_current_section(currentSection);
             return res;
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index b74ef32b..9ddcf503 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -225,7 +225,7 @@ public class CarpetScriptServer
     public boolean addScriptHost(ServerCommandSource source, String name, Function commandValidator,
                                  boolean perPlayer, boolean autoload, boolean isRuleApp)
     {
-        CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet loading", CarpetProfiler.TYPE.GENERAL);
+        CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet load", CarpetProfiler.TYPE.GENERAL);
         if (commandValidator == null) commandValidator = p -> true;
         long start = System.nanoTime();
         name = name.toLowerCase(Locale.ROOT);
diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java
index 436d8632..bc118a41 100644
--- a/src/main/java/carpet/script/utils/ShapesRenderer.java
+++ b/src/main/java/carpet/script/utils/ShapesRenderer.java
@@ -1,6 +1,7 @@
 package carpet.script.utils;
 
 import carpet.CarpetSettings;
+import carpet.utils.CarpetProfiler;
 import com.mojang.blaze3d.systems.RenderSystem;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 import net.minecraft.client.MinecraftClient;
@@ -49,6 +50,7 @@ public class ShapesRenderer
 
     public void render(Camera camera, float partialTick)
     {
+        CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
         //Camera camera = this.client.gameRenderer.getCamera();
         ClientWorld iWorld = this.client.world;
         RegistryKey dimensionType = iWorld.getRegistryKey();
@@ -120,14 +122,17 @@ public class ShapesRenderer
         RenderSystem.defaultBlendFunc();
         RenderSystem.enableTexture();
         RenderSystem.shadeModel(7424);
+        CarpetProfiler.end_current_section(token);
     }
 
     public void addShapes(ListTag tag)
     {
+        CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
         for (int i=0, count = tag.size(); i < count; i++)
         {
             addShape(tag.getCompound(i));
         }
+        CarpetProfiler.end_current_section(token);
     }
 
     public void addShape(CompoundTag tag)
@@ -169,12 +174,14 @@ public class ShapesRenderer
 
     public void renewShapes()
     {
+        CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
         synchronized (shapes)
         {
             shapes.values().forEach(el -> el.values().forEach(shape -> {
                 shape.expiryTick++;
             }));
         }
+        CarpetProfiler.end_current_section(token);
     }
 
     public abstract static class RenderedShape
diff --git a/src/main/java/carpet/utils/CarpetProfiler.java b/src/main/java/carpet/utils/CarpetProfiler.java
index 0ca76092..fd973229 100644
--- a/src/main/java/carpet/utils/CarpetProfiler.java
+++ b/src/main/java/carpet/utils/CarpetProfiler.java
@@ -1,6 +1,5 @@
 package carpet.utils;
 
-import carpet.CarpetSettings;
 import it.unimi.dsi.fastutil.objects.Object2LongMap;
 import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
 import net.minecraft.block.entity.BlockEntityType;
@@ -9,6 +8,7 @@ import net.minecraft.entity.EntityType;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.block.entity.BlockEntity;
 import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.util.Identifier;
 import net.minecraft.util.registry.Registry;
 import net.minecraft.util.registry.RegistryKey;
 import net.minecraft.world.World;
@@ -29,11 +29,13 @@ public class CarpetProfiler
     private static ServerCommandSource currentRequester = null;
     public static int tick_health_requested = 0;
     private static int tick_health_elapsed = 0;
-    private static TYPE test_type = TYPE.NONE; //1 for ticks, 2 for entities;
+    private static TYPE test_type = TYPE.NONE; //1 for ticks, 2 for entities
     private static long current_tick_start = 0;
-    private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks",
-            "Scarpet ace", "Scarpet legacy", "Scarpet events", "Scarpet schedule",
-            "Scarpet command", "Scarpet loading", "Scarpet app data"};
+    private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks", "Carpet"};
+    private static final String[] SCARPET_SECTIONS = {
+            "Scarpet run", "Scarpet events", "Scarpet schedule",
+            "Scarpet command", "Scarpet load", "Scarpet app data", "Scarpet client"
+    };
     private static final String[] SECTIONS = {
             "Spawning and Random Ticks", "Ticket Manager","Unloading",
             "Blocks", "Entities", "Block Entities",
@@ -55,28 +57,15 @@ public class CarpetProfiler
         public final long start;
         public final World world;
 
-        public ProfilerToken(TYPE type, Object section, World world, long start)
+        public ProfilerToken(TYPE type, Object section, World world)
         {
             this.type = type;
             this.section = section;
-            this.start = start;
+            this.start = System.nanoTime();
             this.world = world;
         }
     }
 
-    private static String getSectionString(World world, String section)
-    {
-        return String.format("%s.%s%s",
-                world.getRegistryKey().getValue().getPath(),
-                section,  // ^ getDImension getId
-                world.isClient ? " (Client)" : "");
-    }
-
-    private static String getDimName(RegistryKey world)
-    {
-        return world.getValue().getPath();
-    }
-
     public static void prepare_tick_report(ServerCommandSource source, int ticks)
     {
         //maybe add so it only spams the sending player, but honestly - all may want to see it
@@ -85,17 +74,19 @@ public class CarpetProfiler
         ENTITY_TIMES.clear();
         test_type = TYPE.GENERAL;
         SECTION_STATS.put("tick", 0L);
-        SECTION_STATS.put("Network", 0L);
-        SECTION_STATS.put("Autosave", 0L);
         for (String section : GENERAL_SECTIONS)
         {
             SECTION_STATS.put(section, 0L);
         }
+        for (String section : SCARPET_SECTIONS)
+        {
+            SECTION_STATS.put(section, 0L);
+        }
         for (RegistryKey level : source.getMinecraftServer().getWorldRegistryKeys())
         {
             for (String section : SECTIONS)
             {
-                SECTION_STATS.put(getDimName(level) + "." + section, 0L);
+                SECTION_STATS.put(level.getValue() + "." + section, 0L);
             }
         }
 
@@ -123,21 +114,21 @@ public class CarpetProfiler
     {
         if (tick_health_requested == 0L || test_type != TYPE.GENERAL || current_tick_start == 0)
             return null;
-        return new ProfilerToken(type, name, world, System.nanoTime());
+        return new ProfilerToken(type, name, world);
     }
 
     public static ProfilerToken start_entity_section(World world, Entity e, TYPE type)
     {
         if (tick_health_requested == 0L || test_type != TYPE.ENTITY || current_tick_start == 0)
             return null;
-        return new ProfilerToken(type, e.getType(), world, System.nanoTime());
+        return new ProfilerToken(type, e.getType(), world);
     }
 
     public static ProfilerToken start_block_entity_section(World world, BlockEntity be, TYPE type)
     {
         if (tick_health_requested == 0L || test_type != TYPE.ENTITY || current_tick_start == 0)
             return null;
-        return new ProfilerToken(type, be.getType(), world, System.nanoTime());
+        return new ProfilerToken(type, be.getType(), world);
     }
 
     public static void end_current_section(ProfilerToken tok)
@@ -147,8 +138,10 @@ public class CarpetProfiler
         long end_time = System.nanoTime();
         if (tok.type == TYPE.GENERAL)
         {
-            String current_section = (tok.world == null) ? (String) tok.section :
-                    getSectionString(tok.world, (String)tok.section);
+            World world = tok.world;
+            String current_section = (world == null) ?
+                    (String) tok.section :
+                    String.format("%s.%s%s", world.getRegistryKey().getValue(), tok.section, world.isClient ? " (Client)" : "");
             SECTION_STATS.put(current_section, SECTION_STATS.getOrDefault(current_section, 0L) + end_time - tok.start);
         }
     }
@@ -168,12 +161,10 @@ public class CarpetProfiler
         current_tick_start = System.nanoTime();
     }
 
-    public static void end_tick_profiling(MinecraftServer server, ProfilerToken postTasks)
+    public static void end_tick_profiling(MinecraftServer server)
     {
         if (current_tick_start == 0L)
             return;
-        if (postTasks != null)
-            end_current_section(postTasks);
         SECTION_STATS.put("tick", SECTION_STATS.get("tick") + System.nanoTime() - current_tick_start);
         tick_health_elapsed--;
         if (tick_health_elapsed <= 0)
@@ -212,7 +203,7 @@ public class CarpetProfiler
         long total_tick_time = SECTION_STATS.get("tick");
         double divider = 1.0D / tick_health_requested / 1000000;
         Messenger.m(currentRequester, "w ");
-        Messenger.m(currentRequester, String.format("gi Avg. server tick time: %.3fms", divider * total_tick_time));
+        Messenger.m(currentRequester, "wb Average tick time: ", String.format("yb %.3fms", divider * total_tick_time));
         long accumulated = 0L;
 
         for (String section : GENERAL_SECTIONS)
@@ -221,18 +212,26 @@ public class CarpetProfiler
             if (amount > 0.01)
             {
                 accumulated += SECTION_STATS.get(section);
-                Messenger.m(currentRequester, String.format("gi %s: %.3fms", section, amount));
+                Messenger.m(currentRequester, "w "+section+": ", String.format("y %.3fms", amount));
+            }
+        }
+        for (String section : SCARPET_SECTIONS)
+        {
+            double amount = divider * SECTION_STATS.get(section);
+            if (amount > 0.01)
+            {
+                Messenger.m(currentRequester, "gi "+section+": ", String.format("di %.3fms", amount));
             }
         }
 
         for (RegistryKey dim : server.getWorldRegistryKeys())
         {
-            String dimension = getDimName(dim);
+            Identifier dimensionId = dim.getValue();
             boolean hasSomethin = false;
             for (String section : SECTIONS)
             {
 
-                double amount = divider * SECTION_STATS.getOrDefault(dimension + "." + section, 0L);
+                double amount = divider * SECTION_STATS.getOrDefault(dimensionId + "." + section, 0L);
                 if (amount > 0.01)
                 {
                     hasSomethin = true;
@@ -243,15 +242,16 @@ public class CarpetProfiler
             {
                 continue;
             }
-            Messenger.m(currentRequester, "gi "+dimension + ":");
+            Messenger.m(currentRequester, "wb "+(dimensionId.getNamespace().equals("minecraft")?dimensionId.getPath():dimensionId.toString()) + ":");
             for (String section : SECTIONS)
             {
-                double amount = divider * SECTION_STATS.getOrDefault(dimension + "." + section, 0L);
+                double amount = divider * SECTION_STATS.getOrDefault(dimensionId + "." + section, 0L);
                 if (amount > 0.01)
                 {
-                    if (!(section.endsWith("(Client)")))
-                        accumulated += SECTION_STATS.get(dimension + "." + section);
-                    Messenger.m(currentRequester, String.format("gi  - %s: %.3fms", section, amount));
+                    boolean cli = section.endsWith("(Client)");
+                    if (!cli)
+                        accumulated += SECTION_STATS.get(dimensionId + "." + section);
+                    Messenger.m(currentRequester, String.format("%s - %s: ", cli?"gi":"w", section), String.format("%s %.3fms", cli?"di":"y", amount));
                 }
             }
         }
@@ -263,20 +263,22 @@ public class CarpetProfiler
 
     private static String sectionName(Pair section)
     {
-        String name;
+        Identifier id;
         if (section.getValue() instanceof EntityType)
         {
-            name = Registry.ENTITY_TYPE.getId((EntityType) section.getValue()).getPath();
+            id = Registry.ENTITY_TYPE.getId((EntityType) section.getValue());
         }
         else
         {
-            name = Registry.BLOCK_ENTITY_TYPE.getId((BlockEntityType) section.getValue()).getPath();
+            id = Registry.BLOCK_ENTITY_TYPE.getId((BlockEntityType) section.getValue());
         }
+        String name = "minecraft".equals(id.getNamespace())?id.getPath():id.toString();
         if (section.getKey().isClient)
         {
             name += " (client)";
         }
-        String dim = section.getKey().getRegistryKey().getValue().getPath();
+        Identifier dimkey = section.getKey().getRegistryKey().getValue();
+        String dim = "minecraft".equals(dimkey.getNamespace())?dimkey.getPath():dimkey.toString();
         return name+" in "+dim;
     }
 
@@ -287,31 +289,35 @@ public class CarpetProfiler
         long total_tick_time = SECTION_STATS.get("tick");
         double divider = 1.0D / tick_health_requested / 1000000;
         double divider_1 = 1.0D / (tick_health_requested - 1) / 1000000;
-        Messenger.m(currentRequester, "gi ");
-        Messenger.m(currentRequester, String.format("gi Average tick time: %.3fms", divider * total_tick_time));
+        Messenger.m(currentRequester, "w ");
+        Messenger.m(currentRequester, "wb Average tick time: ", String.format("yb %.3fms", divider * total_tick_time));
         SECTION_STATS.remove("tick");
-        Messenger.m(currentRequester, "gi Top 10 counts:");
+        Messenger.m(currentRequester, "wb Top 10 counts:");
         int total = 0;
         for (Object2LongMap.Entry> sectionEntry : ENTITY_COUNT.object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()))
         {
             if (++total > 10) break;
             Pair section = sectionEntry.getKey();
+            boolean cli = section.getKey().isClient;
             Messenger.m(currentRequester, String.format(
-                    "gi  - %s: %.1f",
-                    sectionName(section),
-                    1.0D * sectionEntry.getLongValue() / (tick_health_requested - (section.getKey().isClient? 1 : 0))
+                    "%s - %s: ", cli?"gi":"w",
+                    sectionName(section)),
+                    String.format("%s %.1f", cli?"di":"y",
+                    1.0D * sectionEntry.getLongValue() / (tick_health_requested - (cli? 1 : 0))
             ));
         }
-        Messenger.m(currentRequester, "gi Top 10 CPU hogs:");
+        Messenger.m(currentRequester, "wb Top 10 CPU hogs:");
         total = 0;
         for (Object2LongMap.Entry> sectionEntry : ENTITY_TIMES.object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()))
         {
             if (++total > 10) break;
             Pair section = sectionEntry.getKey();
+            boolean cli = section.getKey().isClient;
             Messenger.m(currentRequester, String.format(
-                    "gi  - %s: %.2fms",
-                    sectionName(section),
-                    (section.getKey().isClient ? divider : divider_1) * sectionEntry.getLongValue()
+                    "%s - %s: ", cli?"gi":"w",
+                    sectionName(section)),
+                    String.format("%s %.2fms", cli?"di":"y",
+                    (cli ? divider : divider_1) * sectionEntry.getLongValue()
             ));
         }
     }

From 5247bb15a88565a881566a7357833af1a2be1ef2 Mon Sep 17 00:00:00 2001
From: altrisi 
Date: Tue, 6 Apr 2021 22:33:26 +0200
Subject: [PATCH 80/87] Inherit an exception's constructor

---
 .../java/carpet/script/exception/ExpressionException.java   | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/main/java/carpet/script/exception/ExpressionException.java b/src/main/java/carpet/script/exception/ExpressionException.java
index 419fb345..a41cc178 100644
--- a/src/main/java/carpet/script/exception/ExpressionException.java
+++ b/src/main/java/carpet/script/exception/ExpressionException.java
@@ -7,6 +7,7 @@ import carpet.script.Tokenizer;
 import carpet.script.value.FunctionValue;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Supplier;
 
@@ -26,10 +27,7 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
 
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
     {
-        super("Error");
-        lazyMessage = () -> makeMessage(c, e, t, message);
-        token = t;
-        context = c;
+        this(c, e, t, message, Collections.emptyList());
     }
     public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List stack)
     {

From 6fc38d6ec150a2bbce0cee1aae255abe2d70f18b Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Tue, 6 Apr 2021 20:57:14 -0300
Subject: [PATCH 81/87] slashes fix issues with some java versions

---
 src/main/java/carpet/script/argument/FileArgument.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/carpet/script/argument/FileArgument.java b/src/main/java/carpet/script/argument/FileArgument.java
index f25176e2..104f04f1 100644
--- a/src/main/java/carpet/script/argument/FileArgument.java
+++ b/src/main/java/carpet/script/argument/FileArgument.java
@@ -249,7 +249,7 @@ public class FileArgument
             strings = result.map(p -> {
                 if (zipContainer == null)
                     return rootPath.relativize(p).toString().replaceAll("[\\\\/]+", "/");
-                return (zipComponent + p.toString()).replaceAll("[\\\\/]+", "/");
+                return (zipComponent + '/'+ p.toString()).replaceAll("[\\\\/]+", "/");
             });
         } }
         finally

From e0eec1bb9c43a12628bece342add45def9b88764 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Wed, 7 Apr 2021 18:49:53 -0300
Subject: [PATCH 82/87] 1.4.31

---
 gradle.properties                        | 2 +-
 src/main/java/carpet/CarpetSettings.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/gradle.properties b/gradle.properties
index de926908..fa34d7f0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G
 	fabric_version=0.29.4+1.16
 
 # Mod Properties
-	mod_version = 1.4.30
+	mod_version = 1.4.31
 	maven_group = carpet
 	archives_base_name = fabric-carpet
 
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index 20b241e9..8e8b287a 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT;
 @SuppressWarnings("CanBeFinal")
 public class CarpetSettings
 {
-    public static final String carpetVersion = "1.4.30+v210331";
+    public static final String carpetVersion = "1.4.31+v210407";
     public static final Logger LOG = LogManager.getLogger("carpet");
     public static boolean skipGenerationChecks = false;
     public static boolean impendingFillSkipUpdates = false;

From 8373a4611d05f6ef9fc3033c411396be3a828374 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Wed, 7 Apr 2021 19:23:15 +0000
Subject: [PATCH 83/87] Merge docs for 'Carpet Mod 1.4.31 for Minecraft 1.16.5
 and Minecraft 21w14a'

---
 docs/scarpet/Full.md | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index bcb529e5..2bffdb88 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -910,11 +910,12 @@ length('foo') => 3
 ### `rand(expr), rand(expr, seed)`
 
 returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions, 
-boolean functions, or `bool`), returns false if the randomly selected value is less than 1\. This means 
-that `rand(2)` returns true half of the time and `rand(5)` returns true for 80% (4/5) of the time. If seed is not 
-provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the 
-same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number 
-(per app), then your random sequence will revert to the beginning.
+boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means 
+that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not 
+provided, uses a random seed that's shared across all scarpet apps. 
+If seed is provided, each consecutive call to rand() will act like 'next' call to the 
+same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app), 
+so if you exceed this number, your random sequences will revert to the beginning and start over.
 
 
 map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
@@ -5228,7 +5229,7 @@ Available options in the scarpet app space:
   * `world_folder` - name of the direct folder in the saves that holds world files
   * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
   * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
-
+  * `world_spawn_point` - world spawn point
 
  Relevant gameplay related properties
   * `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`

From 7b00952653646275577f2bff3000bc48b84f70af Mon Sep 17 00:00:00 2001
From: BisUmTo <31419791+BisUmTo@users.noreply.github.com>
Date: Thu, 8 Apr 2021 23:05:13 +0200
Subject: [PATCH 84/87] Added onExplosion Scarpet Event (#620)

* Added onExplosion Scarpet Event

* Reverted changes to Full.md

* Grammar corrections by James103

Co-authored-by: James103 <37945304+James103@users.noreply.github.com>

* Separated Explosion Mixins

Applied Ghoulboy78 suggestion.
WARNING: 'blocks' list is now empty if 'explosionNoBlockDamage' carpet rule is set to 'true'

* List Value from Triple

Suggested by Gnembon

* Explosion Mixin priority

Suggestion by Gnembon

* Added affectedEntities parameter

* Updated docs

* Optimizing calls

* Update src/main/java/carpet/helpers/OptimizedExplosion.java

* Update docs/scarpet/api/Events.md

Co-authored-by: gnembon <41132274+gnembon@users.noreply.github.com>

* Update docs/scarpet/api/Events.md

Co-authored-by: gnembon <41132274+gnembon@users.noreply.github.com>

* Update src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java

Co-authored-by: gnembon <41132274+gnembon@users.noreply.github.com>

Co-authored-by: James103 <37945304+James103@users.noreply.github.com>
Co-authored-by: gnembon <41132274+gnembon@users.noreply.github.com>
---
 docs/scarpet/api/Events.md                    |  7 +-
 docs/scarpet/resources/editors/idea/4.txt     |  3 +-
 .../scarpet/resources/editors/npp/scarpet.xml |  1 +
 .../carpet/helpers/OptimizedExplosion.java    | 19 ++++-
 .../mixins/Explosion_scarpetEventMixin.java   | 69 +++++++++++++++++++
 .../java/carpet/script/CarpetEventServer.java | 20 ++++++
 src/main/resources/carpet.mixins.json         |  1 +
 7 files changed, 117 insertions(+), 3 deletions(-)
 create mode 100644 src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java

diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index e646df83..061f00f0 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -79,6 +79,11 @@ at the same time.
 Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would 
 already be spawned at that point. `mode` is `true` if the lightning did cause a trap to spawn. 
 
+### `__on_explosion(pos, power, source, create_fire, blocks, entities)`
+Triggered right before an explosion blows up and any changes to the blocks are done, but the decision to blow up is already made and entities are already affected.  
+The parameter `blocks` contains the list of blocks that will blow up (empty if `explosionNoBlockDamage` is set to `true`).
+The parameter `entities` contains the list of entities that have been affected by the explosion.
+
 ### `__on_carpet_rule_changes(rule, new_value)`
 Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command, 
 which will then be namespaced as `namespace:rule`.
@@ -107,7 +112,7 @@ Example events that may cause it to happen is releasing a bow. The event is trig
 the request, however the `item_tuple` is provided representing the item that the player started with. You can use that and
 compare with the currently held item for a delta.
 
-### `__on_player_finishes_using_item(player, item_tuple, hand))`
+### `__on_player_finishes_using_item(player, item_tuple, hand)`
 Player using of an item is done. This is controlled server side and is responsible for such events as finishing
 eating. The event is triggered after confirming that the action is valid, and sending the feedback back
 to the client, but before triggering it and its effects in game.
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index a921bb46..e11b1bd5 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -42,4 +42,5 @@ __on_player_deals_damage(player, amount, entity) ->
 __on_player_dies(player) -> 
 __on_player_respawns(player) -> 
 __on_player_disconnects(player, reason) -> 
-__on_player_connects(player) ->
+__on_player_connects(player) -> 
+__on_explosion(pos, power, source, create_fire, blocks, entities) ->
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index b10e15e4..4940cfe3 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -139,6 +139,7 @@
                 __on_player_respawns
                 __on_player_disconnects
                 __on_player_connects
+                __on_explosion
             
             $
             00' 01\ 02' 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java
index 1c124ce5..5d164adb 100644
--- a/src/main/java/carpet/helpers/OptimizedExplosion.java
+++ b/src/main/java/carpet/helpers/OptimizedExplosion.java
@@ -48,6 +48,8 @@ import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 import net.minecraft.world.chunk.Chunk;
 
+import static carpet.script.CarpetEventServer.Event.EXPLOSION;
+
 public class OptimizedExplosion
 {
     private static List entitylist;
@@ -68,9 +70,14 @@ public class OptimizedExplosion
     private static ArrayList chances = new ArrayList<>();
     private static BlockPos blastChanceLocation;
 
+    // Creating entity list for scarpet event
+    private static List entityList = new ArrayList<>();
+
     public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) {
         ExplosionAccessor eAccess = (ExplosionAccessor) e;
         
+        entityList.clear();
+boolean eventNeeded = EXPLOSION.isNeeded();
         blastCalc(e);
 
         if (!CarpetSettings.explosionNoBlockDamage) {
@@ -156,6 +163,11 @@ public class OptimizedExplosion
                             densityCache.put(pair, density);
                         }
 
+                        // If it is needed, it saves the entity
+                        if (eventNeeded) {
+                            entityList.add(entity);
+                        }
+
                         double d10 = (1.0D - d12) * density;
                         entity.damage(e.getDamageSource(),
                                 (float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D)));
@@ -195,6 +207,11 @@ public class OptimizedExplosion
         double posY = eAccess.getY();
         double posZ = eAccess.getZ();
 
+        // If it is needed, calls scarpet event
+        if (EXPLOSION.isNeeded()) {
+            EXPLOSION.onExplosion((ServerWorld) world, eAccess.getX(), eAccess.getY(), eAccess.getZ(), eAccess.getPower(), e.getDamageSource(), eAccess.isCreateFire(), e.getAffectedBlocks(), entityList);
+        }
+
         boolean damagesTerrain = eAccess.getDestructionType() != Explosion.DestructionType.NONE;
 
         // explosionSound incremented till disabling the explosion particles and sound
@@ -598,4 +615,4 @@ public class OptimizedExplosion
             );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
new file mode 100644
index 00000000..d0ae56a3
--- /dev/null
+++ b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
@@ -0,0 +1,69 @@
+package carpet.mixins;
+
+import carpet.logging.LoggerRegistry;
+import carpet.logging.logHelpers.ExplosionLogHelper;
+import carpet.script.value.EntityValue;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.damage.DamageSource;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.World;
+import net.minecraft.world.explosion.Explosion;
+import net.minecraft.world.explosion.ExplosionBehavior;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static carpet.script.CarpetEventServer.Event.EXPLOSION;
+
+@Mixin(value = Explosion.class, priority = 990)
+public abstract class Explosion_scarpetEventMixin
+{
+    @Shadow @Final private World world;
+    @Shadow @Final private double x;
+    @Shadow @Final private double y;
+    @Shadow @Final private double z;
+    @Shadow @Final private float power;
+    @Shadow @Final private DamageSource damageSource;
+    @Shadow @Final private boolean createFire;
+    @Shadow @Final private List affectedBlocks;
+    private List affectedEntities;
+
+    @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/damage/DamageSource;Lnet/minecraft/world/explosion/ExplosionBehavior;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V",
+            at = @At(value = "RETURN"))
+    private void onExplosionCreated(World world, Entity entity, DamageSource damageSource, ExplosionBehavior explosionBehavior, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, CallbackInfo ci)
+    {
+        if (EXPLOSION.isNeeded())
+        {
+            affectedEntities = new ArrayList<>();
+        }
+    }
+
+    @Inject(method = "collectBlocksAndDamageEntities", locals= LocalCapture.CAPTURE_FAILHARD, at=@At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z"))
+    private void onExplosion(CallbackInfo ci, Set s1, float f1, int i1, int i2, int i3, int i4, int i5, int i6, List l1, Vec3d v1, int i7, Entity entity)
+    {
+        if (affectedEntities != null)
+        {
+            affectedEntities.add(entity);
+        }
+    }
+
+    @Inject(method = "affectWorld", at = @At("HEAD"))
+    private void onExplosion(boolean spawnParticles, CallbackInfo ci)
+    {
+        if (EXPLOSION.isNeeded())
+        {
+            EXPLOSION.onExplosion((ServerWorld) world, x, y, z, power, damageSource, createFire, affectedBlocks, affectedEntities);
+        }
+    }
+}
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 3c1c084d..7324bb4c 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -865,6 +865,25 @@ public class CarpetEventServer
                 );
             }
         };
+        public static final Event EXPLOSION = new Event("explosion", 6, true)
+        {
+            @Override
+            public void onExplosion(ServerWorld world, double x, double y, double z, float power, DamageSource source, boolean createFire, List affectedBlocks, List affectedEntities)
+            {
+                handler.call(
+                        () -> Arrays.asList(
+                                ListValue.fromTriple(x, y, z),
+                                new NumericValue(power),
+                                source.getAttacker()==null?Value.NULL:new EntityValue(source.getAttacker()),
+                                createFire?Value.TRUE:Value.FALSE,
+                                new ListValue(affectedBlocks.stream().map(
+                                        b -> new BlockValue(world.getBlockState(b),world,b)
+                                ).collect(Collectors.toList())),
+                                new ListValue(affectedEntities.stream().map(EntityValue::new).collect(Collectors.toList()))
+                        ), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
+                );
+            }
+        };
 
         public static String getEntityLoadEventName(EntityType et)
         {
@@ -985,6 +1004,7 @@ public class CarpetEventServer
         public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
         public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
 
+        public void onExplosion(ServerWorld world, double x, double y, double z, float power, DamageSource source, boolean createFire, List affectedBlocks, List affectedEntities) { }
 
         public void onWorldEvent(ServerWorld world, BlockPos pos) { }
         public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index f945eb81..0d18197f 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -129,6 +129,7 @@
     "TntBlockMixin",
     "ExplosionMixin",
     "ExplosionAccessor",
+    "Explosion_scarpetEventMixin",
 
     "TntEntityMixin",
     "ProjectileEntityMixin",

From e063748e831ed5bd917dd9b96d0d4030be3dd5b8 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 9 Apr 2021 01:38:00 -0300
Subject: [PATCH 85/87] added `create_explosion` and more event handlers.

---
 docs/scarpet/api/BlocksAndWorldAccess.md      |  7 ++
 docs/scarpet/api/Events.md                    | 14 ++-
 docs/scarpet/resources/editors/idea/2.txt     |  1 +
 docs/scarpet/resources/editors/idea/4.txt     |  3 +-
 .../scarpet/resources/editors/npp/scarpet.xml |  7 +-
 .../carpet/helpers/OptimizedExplosion.java    | 11 +--
 .../mixins/Explosion_scarpetEventMixin.java   | 20 +++--
 .../mixins/ServerWorld_scarpetMixin.java      | 15 ++++
 .../java/carpet/script/CarpetEventServer.java | 57 ++++++++++--
 .../java/carpet/script/api/WorldAccess.java   | 87 +++++++++++++++++++
 .../java/carpet/script/value/EntityValue.java |  6 ++
 .../assets/carpet/scripts/event_test.sc       | 29 +++++++
 12 files changed, 226 insertions(+), 31 deletions(-)

diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md
index ecfe0cea..4d4529f8 100644
--- a/docs/scarpet/api/BlocksAndWorldAccess.md
+++ b/docs/scarpet/api/BlocksAndWorldAccess.md
@@ -229,6 +229,13 @@ Causes a block to be harvested by a specified player entity. Honors player item
 tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break 
 that block, a block doesn't get destroyed either.
 
+### `create_explosion(pos, power?, mode?, fire?, source?, attacker?)`
+
+Creates an explosion at a given position. Default values of optional parameters are: `'power'` - `4` (TNT power), 
+`'mode'` (block breaking effect `none`, `destroy` or `break`: `break`, `fire` (whether extra fire blocks should be created) - `false`,
+`source` (exploding entity) - `null` and `attacker` (entity responsible for trigerring) - `null`. Explosions created with this
+endpoint cannot be captured with `__on_explosion` event, however they will be captured by `__on_explosion_outcome`.
+
 ### `weather()`,`weather(type)`,`weather(type, ticks)`
 
 If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index 061f00f0..e0a6ffa3 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -79,10 +79,18 @@ at the same time.
 Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would 
 already be spawned at that point. `mode` is `true` if the lightning did cause a trap to spawn. 
 
-### `__on_explosion(pos, power, source, create_fire, blocks, entities)`
-Triggered right before an explosion blows up and any changes to the blocks are done, but the decision to blow up is already made and entities are already affected.  
+### `__on_explosion(pos, power, source, causer, mode, fire)`
+
+Event triggered right before explosion takes place and before has any effect on the world. `source` can be an entity causing
+the explosion, and `causer` the entity triggering it,
+`mode` indicates block effects: `'none'`, `'break'` (drop all blocks), or `'destroy'` - drop few blocks. Event
+is not captured when `create_explosion()` is called.
+
+### `__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)`
+Triggered during the explosion, before any changes to the blocks are done, 
+but the decision to blow up is already made and entities are already affected.  
 The parameter `blocks` contains the list of blocks that will blow up (empty if `explosionNoBlockDamage` is set to `true`).
-The parameter `entities` contains the list of entities that have been affected by the explosion.
+The parameter `entities` contains the list of entities that have been affected by the explosion. Triggered even with `create_explosion()`.
 
 ### `__on_carpet_rule_changes(rule, new_value)`
 Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command, 
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 42a7bf9e..9190666f 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -18,6 +18,7 @@ chunk_tickets
 convert_date
 crafting_remaining_item
 create_datapack
+create_explosion
 create_marker
 current_dimension
 day_time
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index e11b1bd5..388185dc 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -43,4 +43,5 @@ __on_player_dies(player) ->
 __on_player_respawns(player) -> 
 __on_player_disconnects(player, reason) -> 
 __on_player_connects(player) -> 
-__on_explosion(pos, power, source, create_fire, blocks, entities) ->
+__on_explosion(pos, power, source, causer, mode, fire) ->
+__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 4940cfe3..93992ec5 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -55,7 +55,9 @@
                 set without_updates
                 blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi
                 place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets
-                structure_eligibility structures structure_references set_structure reset_chunk reload_chunk create_datapack enable_hidden_dimensions
+                plop harvest destroy create_explosion
+                structure_eligibility structures structure_references set_structure
+                reset_chunk reload_chunk create_datapack enable_hidden_dimensions
                 add_chunk_ticket
 
                 convert_date unix_time
@@ -82,7 +84,7 @@
 
                 run save tick_time world_time day_time last_tick_times game_tick current_dimension in_dimension view_distance
 
-                plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
+                schedule nbt draw_shape create_marker remove_all_markers
 
                 load_app_data store_app_data read_file write_file delete_file list_files
                 escape_nbt parse_nbt encode_nbt tag_matches statistic
@@ -140,6 +142,7 @@
                 __on_player_disconnects
                 __on_player_connects
                 __on_explosion
+                __on_explosion_outcome
             
             $
             00' 01\ 02' 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java
index 5d164adb..50475ab3 100644
--- a/src/main/java/carpet/helpers/OptimizedExplosion.java
+++ b/src/main/java/carpet/helpers/OptimizedExplosion.java
@@ -10,13 +10,10 @@ import java.util.List;
 import carpet.logging.logHelpers.ExplosionLogHelper;
 import carpet.mixins.ExplosionAccessor;
 import carpet.CarpetSettings;
-import carpet.mixins.ExplosionMixin;
 import carpet.utils.Messenger;
-import com.google.common.collect.Lists;
 import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ObjectListIterator;
 import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
 import net.minecraft.block.BlockState;
 import net.minecraft.block.Blocks;
@@ -48,7 +45,7 @@ import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 import net.minecraft.world.chunk.Chunk;
 
-import static carpet.script.CarpetEventServer.Event.EXPLOSION;
+import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
 
 public class OptimizedExplosion
 {
@@ -77,7 +74,7 @@ public class OptimizedExplosion
         ExplosionAccessor eAccess = (ExplosionAccessor) e;
         
         entityList.clear();
-boolean eventNeeded = EXPLOSION.isNeeded();
+        boolean eventNeeded = EXPLOSION_OUTCOME.isNeeded() && !eAccess.getWorld().isClient();
         blastCalc(e);
 
         if (!CarpetSettings.explosionNoBlockDamage) {
@@ -208,8 +205,8 @@ boolean eventNeeded = EXPLOSION.isNeeded();
         double posZ = eAccess.getZ();
 
         // If it is needed, calls scarpet event
-        if (EXPLOSION.isNeeded()) {
-            EXPLOSION.onExplosion((ServerWorld) world, eAccess.getX(), eAccess.getY(), eAccess.getZ(), eAccess.getPower(), e.getDamageSource(), eAccess.isCreateFire(), e.getAffectedBlocks(), entityList);
+        if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient()) {
+            EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, eAccess.getEntity(), e::getCausingEntity,  eAccess.getX(), eAccess.getY(), eAccess.getZ(), eAccess.getPower(), eAccess.isCreateFire(), e.getAffectedBlocks(), entityList, eAccess.getDestructionType());
         }
 
         boolean damagesTerrain = eAccess.getDestructionType() != Explosion.DestructionType.NONE;
diff --git a/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
index d0ae56a3..8bca895d 100644
--- a/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
+++ b/src/main/java/carpet/mixins/Explosion_scarpetEventMixin.java
@@ -1,9 +1,7 @@
 package carpet.mixins;
 
-import carpet.logging.LoggerRegistry;
-import carpet.logging.logHelpers.ExplosionLogHelper;
-import carpet.script.value.EntityValue;
 import net.minecraft.entity.Entity;
+import net.minecraft.entity.LivingEntity;
 import net.minecraft.entity.damage.DamageSource;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.util.math.BlockPos;
@@ -11,6 +9,7 @@ import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 import net.minecraft.world.explosion.Explosion;
 import net.minecraft.world.explosion.ExplosionBehavior;
+import org.jetbrains.annotations.Nullable;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
@@ -20,11 +19,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
-import static carpet.script.CarpetEventServer.Event.EXPLOSION;
+import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
 
 @Mixin(value = Explosion.class, priority = 990)
 public abstract class Explosion_scarpetEventMixin
@@ -34,16 +32,20 @@ public abstract class Explosion_scarpetEventMixin
     @Shadow @Final private double y;
     @Shadow @Final private double z;
     @Shadow @Final private float power;
-    @Shadow @Final private DamageSource damageSource;
     @Shadow @Final private boolean createFire;
     @Shadow @Final private List affectedBlocks;
+    @Shadow @Final private Explosion.DestructionType destructionType;
+    @Shadow @Final private @Nullable Entity entity;
+
+    @Shadow /*@Nullable*/ public abstract /*@Nullable*/ LivingEntity getCausingEntity();
+
     private List affectedEntities;
 
     @Inject(method = "(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/damage/DamageSource;Lnet/minecraft/world/explosion/ExplosionBehavior;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V",
             at = @At(value = "RETURN"))
     private void onExplosionCreated(World world, Entity entity, DamageSource damageSource, ExplosionBehavior explosionBehavior, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, CallbackInfo ci)
     {
-        if (EXPLOSION.isNeeded())
+        if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
         {
             affectedEntities = new ArrayList<>();
         }
@@ -61,9 +63,9 @@ public abstract class Explosion_scarpetEventMixin
     @Inject(method = "affectWorld", at = @At("HEAD"))
     private void onExplosion(boolean spawnParticles, CallbackInfo ci)
     {
-        if (EXPLOSION.isNeeded())
+        if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
         {
-            EXPLOSION.onExplosion((ServerWorld) world, x, y, z, power, damageSource, createFire, affectedBlocks, affectedEntities);
+            EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, entity, this::getCausingEntity, x, y, z, power, createFire, affectedBlocks, affectedEntities, destructionType);
         }
     }
 }
diff --git a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
index 5e83e881..168fff49 100644
--- a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
@@ -5,20 +5,27 @@ import carpet.fakes.ServerWorldInterface;
 import carpet.script.CarpetEventServer;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.LightningEntity;
+import net.minecraft.entity.damage.DamageSource;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.ChunkPos;
 import net.minecraft.util.profiler.Profiler;
 import net.minecraft.world.chunk.WorldChunk;
+import net.minecraft.world.explosion.Explosion;
+import net.minecraft.world.explosion.ExplosionBehavior;
 import net.minecraft.world.level.ServerWorldProperties;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
 import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
+import static carpet.script.CarpetEventServer.Event.EXPLOSION;
 import static carpet.script.CarpetEventServer.Event.LIGHTNING;
 
 @Mixin(ServerWorld.class)
@@ -57,6 +64,14 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
         }
     }
 
+    @Inject(method = "createExplosion", at = @At("HEAD"))
+    private void handleExplosion(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionBehavior explosionBehavior, double d, double e, double f, float g, boolean bl, Explosion.DestructionType destructionType, CallbackInfoReturnable cir)
+    {
+        if (EXPLOSION.isNeeded())
+            EXPLOSION.onExplosion((ServerWorld) (Object)this, entity, null, d, e, f, g, bl, null, null, destructionType);
+    }
+
+    @Final
     @Shadow
     private ServerWorldProperties worldProperties;
     public ServerWorldProperties getWorldPropertiesCM(){
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 7324bb4c..18bd5df0 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -5,6 +5,7 @@ import carpet.CarpetSettings;
 import carpet.helpers.TickSpeed;
 import carpet.script.exception.InternalExpressionException;
 import carpet.script.value.BlockValue;
+import carpet.script.value.BooleanValue;
 import carpet.script.value.EntityValue;
 import carpet.script.value.FunctionValue;
 import carpet.script.value.ListValue;
@@ -19,8 +20,11 @@ import carpet.utils.Messenger;
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
 import net.minecraft.block.BlockState;
 import net.minecraft.entity.EntityType;
+import net.minecraft.entity.LivingEntity;
+import net.minecraft.entity.TntEntity;
 import net.minecraft.entity.damage.DamageSource;
 import net.minecraft.entity.passive.MerchantEntity;
+import net.minecraft.entity.projectile.ProjectileEntity;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.command.ServerCommandSource;
 import net.minecraft.entity.Entity;
@@ -42,6 +46,7 @@ import net.minecraft.village.Merchant;
 import net.minecraft.village.TradeOffer;
 import net.minecraft.world.World;
 import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.explosion.Explosion;
 import org.apache.commons.lang3.tuple.Pair;
 
 import java.util.ArrayList;
@@ -52,6 +57,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -865,21 +871,55 @@ public class CarpetEventServer
                 );
             }
         };
-        public static final Event EXPLOSION = new Event("explosion", 6, true)
+        //copy of Explosion.getCausingEntity() #TRACK#
+        private static LivingEntity getExplosionCausingEntity(Entity entity)
+        {
+            if (entity == null)  return null;
+            else if (entity instanceof TntEntity) return ((TntEntity)entity).getCausingEntity();
+            else if (entity instanceof LivingEntity) return (LivingEntity)entity;
+            else if (entity instanceof ProjectileEntity) {
+                Entity owner = ((ProjectileEntity)entity).getOwner();
+                if (owner instanceof LivingEntity) return (LivingEntity)owner;
+            }
+            return null;
+        }
+
+        public static final Event EXPLOSION_OUTCOME = new Event("explosion_outcome", 8, true)
         {
             @Override
-            public void onExplosion(ServerWorld world, double x, double y, double z, float power, DamageSource source, boolean createFire, List affectedBlocks, List affectedEntities)
+            public void onExplosion(ServerWorld world, Entity e,  Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type)
             {
                 handler.call(
                         () -> Arrays.asList(
                                 ListValue.fromTriple(x, y, z),
-                                new NumericValue(power),
-                                source.getAttacker()==null?Value.NULL:new EntityValue(source.getAttacker()),
-                                createFire?Value.TRUE:Value.FALSE,
-                                new ListValue(affectedBlocks.stream().map(
+                                NumericValue.of(power),
+                                EntityValue.of(e),
+                                EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
+                                StringValue.of(type.name().toLowerCase(Locale.ROOT)),
+                                BooleanValue.of(createFire),
+                                new ListValue(affectedBlocks.stream().filter(b -> !world.isAir(b)). map( // da heck they send air blocks
                                         b -> new BlockValue(world.getBlockState(b),world,b)
                                 ).collect(Collectors.toList())),
-                                new ListValue(affectedEntities.stream().map(EntityValue::new).collect(Collectors.toList()))
+                                new ListValue(affectedEntities.stream().map(EntityValue::of).collect(Collectors.toList()))
+                        ), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
+                );
+            }
+        };
+
+
+        public static final Event EXPLOSION = new Event("explosion", 6, true)
+        {
+            @Override
+            public void onExplosion(ServerWorld world, Entity e, Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type)
+            {
+                handler.call(
+                        () -> Arrays.asList(
+                                ListValue.fromTriple(x, y, z),
+                                NumericValue.of(power),
+                                EntityValue.of(e),
+                                EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
+                                StringValue.of(type.name().toLowerCase(Locale.ROOT)),
+                                BooleanValue.of(createFire)
                         ), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
                 );
             }
@@ -1004,8 +1044,7 @@ public class CarpetEventServer
         public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
         public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
 
-        public void onExplosion(ServerWorld world, double x, double y, double z, float power, DamageSource source, boolean createFire, List affectedBlocks, List affectedEntities) { }
-
+        public void onExplosion(ServerWorld world, Entity e,  Supplier attacker, double x, double y, double z, float power, boolean createFire, List affectedBlocks, List affectedEntities, Explosion.DestructionType type) { }
         public void onWorldEvent(ServerWorld world, BlockPos pos) { }
         public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
         public void onCarpetRuleChanges(ParsedRule rule, ServerCommandSource source) { }
diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java
index 1d81c5b5..40f9c3ce 100644
--- a/src/main/java/carpet/script/api/WorldAccess.java
+++ b/src/main/java/carpet/script/api/WorldAccess.java
@@ -50,6 +50,7 @@ import net.minecraft.enchantment.Enchantments;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.EntityType;
 import net.minecraft.entity.FallingBlockEntity;
+import net.minecraft.entity.LivingEntity;
 import net.minecraft.entity.ai.pathing.NavigationType;
 import net.minecraft.item.BlockItem;
 import net.minecraft.item.Item;
@@ -61,6 +62,7 @@ import net.minecraft.item.SwordItem;
 import net.minecraft.item.TridentItem;
 import net.minecraft.nbt.CompoundTag;
 import net.minecraft.nbt.Tag;
+import net.minecraft.network.packet.s2c.play.ExplosionS2CPacket;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.world.ChunkTicket;
 import net.minecraft.server.world.ChunkTicketType;
@@ -79,6 +81,7 @@ import net.minecraft.util.math.BlockBox;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.ChunkPos;
 import net.minecraft.util.math.Direction;
+import net.minecraft.util.math.Vec3d;
 import net.minecraft.util.registry.Registry;
 import net.minecraft.world.Heightmap;
 import net.minecraft.world.LightType;
@@ -88,6 +91,7 @@ import net.minecraft.world.biome.Biome;
 import net.minecraft.world.chunk.Chunk;
 import net.minecraft.world.chunk.ChunkStatus;
 import net.minecraft.world.chunk.WorldChunk;
+import net.minecraft.world.explosion.Explosion;
 import net.minecraft.world.gen.ChunkRandom;
 import net.minecraft.world.gen.chunk.ChunkGenerator;
 import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
@@ -96,6 +100,7 @@ import net.minecraft.world.level.ServerWorldProperties;
 import net.minecraft.world.poi.PointOfInterest;
 import net.minecraft.world.poi.PointOfInterestStorage;
 import net.minecraft.world.poi.PointOfInterestType;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -970,6 +975,88 @@ public class WorldAccess {
             return success ? LazyValue.TRUE : LazyValue.FALSE;
         });
 
+        expression.addLazyFunction("create_explosion", -1, (c, t, lv) ->
+        {
+            if (lv.isEmpty())
+                throw new InternalExpressionException("'create_explosion' requires at least a position to explode");
+            CarpetContext cc = (CarpetContext)c;
+            float powah = 4.0f;
+            Explosion.DestructionType mode = Explosion.DestructionType.BREAK;
+            boolean createFire = false;
+            Entity source = null;
+            LivingEntity attacker = null;
+            Vector3Argument location = Vector3Argument.findIn(cc, lv, 0, false, true);
+            Vec3d pos = location.vec;
+            if (lv.size() > location.offset)
+            {
+                powah = NumericValue.asNumber(lv.get(location.offset).evalValue(c), "explosion power").getFloat();
+                if (powah < 0) throw new InternalExpressionException("Explosion power cannot be negative");
+                if (lv.size() > location.offset+1)
+                {
+                    String strval = lv.get(location.offset+1).evalValue(c).getString();
+                    try {
+                        mode = Explosion.DestructionType.valueOf(strval.toUpperCase(Locale.ROOT));
+                    }
+                    catch (IllegalArgumentException ile) { throw new InternalExpressionException("Illegal explosions block behaviour: "+strval); }
+                    if (lv.size() > location.offset+2)
+                    {
+                        createFire = lv.get(location.offset+2).evalValue(c, Context.BOOLEAN).getBoolean();
+                        if (lv.size() > location.offset+3)
+                        {
+                            Value enVal= lv.get(location.offset+3).evalValue(c);
+                            if (enVal.isNull()) {} // is null already
+                            else if (enVal instanceof EntityValue)
+                            {
+                                source = ((EntityValue) enVal).getEntity();
+                            }
+                            else
+                            {
+                                throw new InternalExpressionException("Fourth parameter of the explosion has to be an entity, not "+enVal.getTypeString());
+                            }
+                            if (lv.size() > location.offset+4)
+                            {
+                                enVal = lv.get(location.offset+4).evalValue(c);
+                                if (enVal.isNull()) {} // is null already
+                                else if (enVal instanceof EntityValue)
+                                {
+                                    Entity attackingEntity =  ((EntityValue) enVal).getEntity();
+                                    if (attackingEntity instanceof LivingEntity)
+                                    {
+                                        attacker = (LivingEntity) attackingEntity;
+                                    }
+                                    else throw new InternalExpressionException("Attacking entity needs to be a living thing, "+
+                                            ValueConversions.of(Registry.ENTITY_TYPE.getId(attackingEntity.getType())).getString() +" ain't it.");
+
+                                }
+                                else
+                                {
+                                    throw new InternalExpressionException("Fifth parameter of the explosion has to be a living entity, not "+enVal.getTypeString());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            LivingEntity theAttacker = attacker;
+            float thePowah = powah;
+
+            // copy of ServerWorld.createExplosion #TRACK#
+            Explosion explosion = new Explosion(cc.s.getWorld(), source, null, null, pos.x, pos.y, pos.z, powah, createFire, mode){
+                @Override
+                public @Nullable LivingEntity getCausingEntity() {
+                    return theAttacker;
+                }
+            };
+            explosion.collectBlocksAndDamageEntities();
+            explosion.affectWorld(false);
+            if (mode == Explosion.DestructionType.NONE) explosion.clearAffectedBlocks();
+            cc.s.getWorld().getPlayers().forEach(spe -> {
+                if (spe.squaredDistanceTo(pos) < 4096.0D)
+                    spe.networkHandler.sendPacket(new ExplosionS2CPacket(pos.x, pos.y, pos.z, thePowah, explosion.getAffectedBlocks(), explosion.getAffectedPlayers().get(spe)));
+            });
+            return LazyValue.TRUE;
+        });
+
         // TODO rename to use_item
         expression.addLazyFunction("place_item", -1, (c, t, lv) ->
         {
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 110a5ce4..3f6dd0d9 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -99,6 +99,12 @@ public class EntityValue extends Value
         entity = e;
     }
 
+    public static Value of(Entity e)
+    {
+        if (e == null) return Value.NULL;
+        return new EntityValue(e);
+    }
+
     private static final Map selectorCache = new HashMap<>();
     public static Collection getEntitiesFromSelector(ServerCommandSource source, String selector)
     {
diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc
index 35fca5de..2d0bfc14 100644
--- a/src/main/resources/assets/carpet/scripts/event_test.sc
+++ b/src/main/resources/assets/carpet/scripts/event_test.sc
@@ -328,3 +328,32 @@ __on_close() ->
     print('App is closing for '+player());
     logger('App is closing for '+player());
 );
+
+__on_explosion(pos, power, source, causer, mode, fire) ->
+(
+    print('');
+    print('__on_explosion(pos, power, source, causer, mode, fire)');
+    print('explosion:');
+    print('  - pos: '+pos);
+    print('  - power: '+power);
+    print('  - source: '+source);
+    print('  - causer: '+causer);
+    print('  - mode: '+mode);
+    print('  - fire: '+fire);
+
+);
+
+__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
+(
+    print('');
+    print('__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)');
+    print('explosion outcome:');
+    print('  - pos: '+pos);
+    print('  - power: '+power);
+    print('  - source: '+source);
+    print('  - causer: '+causer);
+    print('  - mode: '+mode);
+    print('  - fire: '+fire);
+    print('  - number or affected blocks: '+length(blocks));
+    print('  - number of affected entities: '+length(entities));
+);

From c264011646324fe5cbb16cdcc360425af799a29f Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 9 Apr 2021 01:43:54 -0300
Subject: [PATCH 86/87] cleanup

---
 src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
index 168fff49..48f39586 100644
--- a/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java
@@ -14,7 +14,6 @@ import net.minecraft.world.chunk.WorldChunk;
 import net.minecraft.world.explosion.Explosion;
 import net.minecraft.world.explosion.ExplosionBehavior;
 import net.minecraft.world.level.ServerWorldProperties;
-import org.jetbrains.annotations.Nullable;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
@@ -65,7 +64,7 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
     }
 
     @Inject(method = "createExplosion", at = @At("HEAD"))
-    private void handleExplosion(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionBehavior explosionBehavior, double d, double e, double f, float g, boolean bl, Explosion.DestructionType destructionType, CallbackInfoReturnable cir)
+    private void handleExplosion(/*@Nullable*/ Entity entity, /*@Nullable*/ DamageSource damageSource, /*@Nullable*/ ExplosionBehavior explosionBehavior, double d, double e, double f, float g, boolean bl, Explosion.DestructionType destructionType, CallbackInfoReturnable cir)
     {
         if (EXPLOSION.isNeeded())
             EXPLOSION.onExplosion((ServerWorld) (Object)this, entity, null, d, e, f, g, bl, null, null, destructionType);

From cd9a5b96c2a6197dac493ce5e1cb81924d7833b9 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sat, 10 Apr 2021 14:53:48 -0300
Subject: [PATCH 87/87] fix superhot not working on singleplayer when riding
 stuffs

---
 src/main/java/carpet/helpers/TickSpeed.java      | 1 +
 src/main/java/carpet/mixins/World_tickMixin.java | 5 ++++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/main/java/carpet/helpers/TickSpeed.java b/src/main/java/carpet/helpers/TickSpeed.java
index a71301fb..d3e81523 100644
--- a/src/main/java/carpet/helpers/TickSpeed.java
+++ b/src/main/java/carpet/helpers/TickSpeed.java
@@ -74,6 +74,7 @@ public class TickSpeed
         if (player_active_timeout < PLAYER_GRACE)
         {
             player_active_timeout = PLAYER_GRACE;
+            ServerNetworkHandler.updateTickPlayerActiveTimeoutToConnectedPlayers();
         }
     }
 
diff --git a/src/main/java/carpet/mixins/World_tickMixin.java b/src/main/java/carpet/mixins/World_tickMixin.java
index 11b9a113..6fe4b6a5 100644
--- a/src/main/java/carpet/mixins/World_tickMixin.java
+++ b/src/main/java/carpet/mixins/World_tickMixin.java
@@ -9,7 +9,9 @@ import net.minecraft.entity.EntityType;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.util.profiler.Profiler;
 import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.Redirect;
@@ -24,6 +26,7 @@ import java.util.function.Consumer;
 @Mixin(World.class)
 public abstract class World_tickMixin implements WorldInterface
 {
+    @Shadow @Final public boolean isClient;
     CarpetProfiler.ProfilerToken currentSection;
     CarpetProfiler.ProfilerToken entitySection;
 
@@ -79,7 +82,7 @@ public abstract class World_tickMixin implements WorldInterface
     @Inject(method = "tickEntity", at = @At("HEAD"), cancellable = true)
     private void startEntity(Consumer consumer_1, Entity e, CallbackInfo ci)
     {
-        if (!TickSpeed.process_entities && !(e instanceof PlayerEntity))
+        if (!(TickSpeed.process_entities || (e instanceof PlayerEntity) || (TickSpeed.is_superHot && isClient && e.getPrimaryPassenger() instanceof PlayerEntity)))
             ci.cancel();
         entitySection =  CarpetProfiler.start_entity_section((World) (Object) this, e, CarpetProfiler.TYPE.ENTITY);
     }