diff --git a/LICENSE b/LICENSE index 0a041280..467d914a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,165 +1,21 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 +MIT License - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. +Copyright (c) 2020 gnembon +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/scarpet/Documentation.md b/docs/scarpet/Documentation.md index 99556222..d56ad074 100644 --- a/docs/scarpet/Documentation.md +++ b/docs/scarpet/Documentation.md @@ -32,7 +32,7 @@ [Events](/docs/scarpet/api/Events.md) -[Scoreboard manipulation](docs/scarpet/api/Scoreboard.md) +[Scoreboard manipulation](/docs/scarpet/api/Scoreboard.md) [Auxiliary Functions](/docs/scarpet/api/Auxiliary.md) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 22deab0d..9e176251 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1,15 +1,9 @@ -# Fundamental components of `scarpet` programming language (towards version 1.7). +# Fundamental components of `scarpet` programming language. Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide the ability to write custom programs to run within Minecraft and interact with the world. -The project was initially built based on the EvalEx project, however it now diverged -so far away from the original implementation, it would be hard to tell it without -this mention. EvalEx is a handy expression evaluator for Java, that allows to evaluate -simple mathematical and boolean expressions. For more information, -see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) - This specification is divided into two sections: this one is agnostic to any Minecraft related features and could function on its own, and CarpetExpression for Minecraft specific routines and world manipulation functions. @@ -277,6 +271,25 @@ check_foo_not_zero(); ... check_foo_not_zero() -> if(global_foo == 0, global_foo = 1) + +## Scarpet preprocessor + +There are several preprocessing operations applied to the source of your program to clean it up and prepare for +execution. Some of them will affect your code as it is reported via stack traces and function definition, and some +are applied only on the surface. + - stripping `//` comments (in file mode) + - replacing `$` with newlines (in command mode, modifies submitted code) + - removing extra semicolons that don't follow `;` use as a binary operator, allowing for lenient use of semicolons + - translating `{` into `m(`, `[` into `l(`, and `]` and `}` into `)` + +No further optimizations are currently applied to your code. + +## Mentions + +LR1 parser, tokenizer, and several built-in functions are built based on the EvalEx project. +EvalEx is a handy expression evaluator for Java, that allows to evaluate +simple mathematical and boolean expressions. EvalEx is distributed under MIT licence. +For more information, see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) # Variables and Constants `scarpet` provides a number of constants that can be used literally in scripts @@ -296,9 +309,9 @@ block positions. All variables starting with `_` are read-only, and cannot be de `scarpet` accepts numeric and string liters constants. Numbers look like `1, 2.5, -3e-7, 0xff,` and are internally represented as Java's `double` but `scarpet` will try to trim trailing zeros as much as possible so if you need to -use them as intergers, you can. Strings using single quoting, for multiple reasons, but primarily to allow for +use them as intergers, you can. Strings use single quoting, for multiple reasons, but primarily to allow for easier use of strings inside doubly quoted command arguments (when passing a script as a parameter of `/script fill` -for example, or when typing in jsons inside scarpet to feed back into a `/data merge` command for example. +for example), or when typing in jsons inside scarpet (to feed back into a `/data merge` command for example). Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions
@@ -882,10 +895,13 @@ Thou shall not sample from noise changing seed frequently. Scarpet will keep tra
 used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more 
 than 256 seeds at the same time, switching between them can get much more expensive.
 
-### `print(expr)`
+### `print(expr)`, `print(player, expr)`
 
 prints the value of the expression to chat. Passes the result of the argument to the output unchanged, 
-so `print`-statements can be weaved in code to debug programming issues
+so `print`-statements can be weaved in code to debug programming issues. By default it uses the same communication
+channels that most vanilla commands are using.
+
+In case player is directly specified, it only sends the message to that player, like `tell` command.
 
 
 print('foo') => results in foo, prints: foo
@@ -918,6 +934,52 @@ flip_my_world_upside_down();
 print(str('this took %d milliseconds',time()-start_time))
 
+### `unix_time()` + +Returns standard POSIX time as a number of milliseconds since the start of the epoch +(00:00 am and 0 seconds, 1 Jan 1970). +Unlike the previous function, this can be used to get exact time, but it varies from time zone to time zone. + +### `convert_date(milliseconds)` +### `convert_date(year, month, date, hours?, mins?, secs?)` +### `convert_date(l(year, month, date, hours?, mins?, secs?))` + +If called with a single argument, converts standard POSIX time to a list in the format: + +`l(year, month, date, hours, mins, secs, day_of_week, day_of_year, week_of_year)` + +eg: `convert_date(1592401346960) -> [2020, 6, 17, 10, 42, 26, 3, 169, 25]` + +Where the `6` stands for June, but `17` stands for 17th, `10` stands for 10am, +`42` stands for 42 minutes past the hour, and `26` stands for 26 seconds past the minute, +and `3` stands for Wednesday, `169` is the day of year, and `25` is a week of year. + +Run `convert_date(unix_time())` to get current time as list. + + +When called with a list, or with 3 or 6 arguments, returns standard POSIX time as a number of milliseconds since the + start of the epoch (1 Jan 1970), +using the time inputted into the function as opposed to the system time. + +Example editing: +
+date = convert_date(unix_time());
+
+months = l('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
+
+days = l('Mon','Tue','Wed','Thu','Fri','Sat','Sun');
+
+print(
+  str('Its %s, %d %s %d, %02d:%02d:%02d', 
+    days:(date:6-1), date:2, months:(date:1-1), date:0, date:3, date:4, date:5 
+  )
+)  
+
+ +This will give you a date: + +It is currently `hrs`:`mins` and `secs` seconds on the `date`th of `month`, `year` + ### `profile_expr(expression)` Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. @@ -1183,6 +1245,8 @@ a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1,2 ### `Operator ->` +`->` operator has two uses - as a function definition operator and key-value initializer for maps. + To organize code better than a flat sequence of operations, one can define functions. Definition is correct if has the following form @@ -1207,7 +1271,17 @@ a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3]
In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, -it needs to use `outer` function in function signature +it needs to use `outer` function in function signature. + +in map construction context (directly in `m()` or `{}`), the `->` operator has a different function by converting its +arguments to a tuple which is used by map constructor as a key-value pair: + +
+{ 'foo' -> 'bar' } => {l('foo', 'bar')}
+
+ +This means that it is not possible to define literally a set of inline function, however a set of functions can still +be created by adding elements to an empty set, and building it this way. ### `outer(arg)` @@ -1423,26 +1497,31 @@ tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[ ## List operations -### `l(values ...), l(iterator)` +### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `l(iterator)` Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all elements are variables, you coujld use it to return multiple results from one function call, if that -function returns a list of results with the same size as the `l` call uses. In case there is only one +function returns a list of results with the same size as the `[]` call uses. In case there is only one argument and it is an iterator (vanilla expression specification has `range`, but Minecraft API implements a bunch of them, like `diamond`), it will convert it to a proper list. Iterators can only be used in high order -functions, and are treated as empty lists, unless unrolled with `l` +functions, and are treated as empty lists, unless unrolled with `[]`. + +Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to +`l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]` +not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid +although not recommended as they will make your code far less readable.
-l(1,2,'foo') => [1, 2, foo]
-l() => [] (empty list)
-l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-l(1, 2) = l(3, 4) => Error: l is not a variable
-l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
-l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz)  => [3, 4, 5]
+l(1,2,'foo') <=> [1, 2, 'foo']
+l() <=> [] (empty list)
+[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+[1, 2] = [3, 4] => Error: l is not a variable
+[foo, bar] = [3, 4]; foo==3 && bar==4 => 1
+[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz]  => [3, 4, 5]
 
-In the last example `l(min(foo, bar), baz)` creates a valid L-value, as min(foo, bar) finds the lower of the -variables (in this case `foo`) creating a valid assignable L-list of [foo, baz], and these values +In the last example `[min(foo, bar), baz]` creates a valid L-value, as `min(foo, bar)` finds the lower of the +variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values will be assigned new values ### `join(delim, list), join(delim, values ...)` @@ -1516,29 +1595,37 @@ range(5,10) => [5, 6, 7, 8, 9] range(20, 10, -2) => [20, 18, 16, 14, 12] -### `element(list, index)(deprecated)` - -Legacy support for older method that worked only on lists. Please use `get(...)` for equivalent support, or `.` -operator. Also previous unique behaviours with `put` on lists has been removed to support all type of -container types. - ## Map operations Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. Apart from container access functions, (`. , get, put, has, delete`), the following functions: -### `m(values ...), m(iterator), m(key_value_pairs)` +### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))` creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value +In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler +syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to +`{l(foo, bar), l(baz, quux)}`, which is equivalent to somewhat older, but more traditional functional form of +`m(l(foo, bar),l(baz, quuz))`. + +Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to +`m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}` +not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid +although not recommended as they will make your code far less readable. + +When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT +notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however +does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`. +
-m(1,2,'foo') => {1: null, 2: null, foo: null}
-m() => {} (empty map)
-m(range(10)) => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
-m(l(1, 2), l(3, 4)) => {1: 2, 3: 4}
-reduce(range(10), put(_a, _, _*_); _a, m())
+{1, 2, 'foo'} => {1: null, 2: null, foo: null}
+m() <=> {} (empty map)
+{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
+m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
+reduce(range(10), put(_a, _, _*_); _a, {})
      => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
 
@@ -1595,11 +1682,14 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block` All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the functions referenced below refer to either method of passing block position. -### `set(pos, block, property?, value?, ...)` +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. Second part, `block` is either block value as a result of `block()` function string value indicating the block name, -and optional `property - value` pairs for extra block properties. If `block` is specified only by name, then if a +and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to +be set for the target block. + +If `block` is specified only by name, then if a destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra properties. @@ -1623,11 +1713,15 @@ set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data +set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same ### `without_updates(expr)` -Evaluates subexpression without causing updates when blocks change in the world +Evaluates subexpression without causing updates when blocks change in the world. + +For synchronization sake, as well as from the fact that suppressed update can only happen within a tick, +the call to the `expr` is docked on the main server task. Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perlin noise generator. The following code causes a cascading effect as blocks placed on chunk borders will cause other chunks to get @@ -2281,7 +2375,7 @@ This function is tentative - will likely change when chunk ticket API is properl These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. -### `scan(cx, cy, cz, dx, dy, dz, px?, py?, pz?, expr)`, `scan(center, range, lower_range?, expr)` +### `scan(center, range, lower_range?, expr)` Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in @@ -2297,14 +2391,15 @@ which would cause the expression not be evaluated for their boolean value. `scan` also handles `continue` and `break` statements, using `continue`'s return value to use in place of expression return value. `break` return value has no effect. -### `volume(x1, y1, z1, x2, y2, z2, expr)` +### `volume(from_pos, to_pos, expr)` Evaluates expression for each block in the area, the same as the `scan` function, but using two opposite corners of the rectangular cuboid. Any corners can be specified, its like you would do with `/fill` command. +You can use a position or three coordinates to specify, it doesn't matter. For return value and handling `break` and `continue` statements, see `scan` function above. -### `neighbours(x, y, z), neighbours(block), neighbours(l(x,y,z))` +### `neighbours(pos)` Returns the list of 6 neighbouring blocks to the argument. Commonly used with other loop functions like `for`. @@ -2312,13 +2407,18 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block -### `rect(cx, cy, cz, dx?, dy?, dz?, px?, py?, pz?)` +### `rect(centre, range?, positive_range?)` -returns an iterator, just like `range` function that iterates over rectangular cubarea of blocks. If only center -point is specified, it iterates over 27 blocks. If `d` arguments are specified, expands selection by the respective -number of blocks in each direction. If `p` arguments are specified, it uses `d` for negative offset, and `p` for positive. +Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center +point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective +number of blocks in each direction. If `positive_range` arguments are specified, + it uses `range` for negative offset, and `positive_range` for positive. -### `diamond(cx, cy, cz, radius?, height?)` +`centre` can be defined either as three coordinates, a list of three coords, or a block value. +`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center +as range instead of taking the values as is.` + +### `diamond(centre_pos, radius?, height?)` Iterates over a diamond like area of blocks. With no radius and height, its 7 blocks centered around the middle (block + neighbours). With a radius specified, it expands shape on x and z coords, and with a custom height, on y. @@ -2857,6 +2957,15 @@ Modifies directly player raw hunger components. Has no effect on non-players adds exhaustion value to the current player exhaustion level - that's the method you probably want to use to manipulate how much 'food' some action cost. +### `modify(e, 'nbt_merge', partial_tag)` + +Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players + +### `modify(e, 'nbt', tag)` + +Reloads the entity from a supplied tag. Better get a valid entity tag, what can go wrong. Wonder what would happen if +transplant rabbit's brain into a villager. Cannot be applied to players. + ## Entity Events There is a number of events that happen to entities that you can attach your own code to in the form of event handlers. @@ -2940,6 +3049,51 @@ item_category('ender_pearl') => misc item_category('stone') => building_blocks +### `recipe_data(item, type?)`, `recipe_data(recipe, type?)` + +returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items +that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its +direct name can be different. + +Recipe type can take one of the following options: + * `'crafting'` - default, crafting table recipe + * `'smelting'` - furnace recipe + * `'blasting'` - blast furnace recipe + * `'smoking'` - smoker recipe + * `'campfire_cooking'` - campfire recipe + * `'stonecutting'` - stonecutter recipe + * `'smithing'` - smithing table (1.16+) + + The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of + an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the + ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left + empty, and recipe specification as another list. Possible recipe specs is: + * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3. + * `['shapeless']` - shapeless crafting + * `['smelting', duration, xp]` - smelting/cooking recipes + * `['cutting']` - stonecutter recipe + * `['special']` - special crafting recipe, typically not present in the crafting menu + * `['custom']` - other recipe types + +Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one +of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it +can accept items of any tags. + +Also note that some recipes leave some products in the crafting window, and these can be determined using + `crafting_remaining_item()` function + + Examples: +
+ recipe_data('iron_ingot_from_nuggets')
+ recipe_data('iron_ingot')
+ recipe_data('glass', 'smelting')
+ 
+ +### `crafting_remaining_item(item)` + +returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an +item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles. + ### `inventory_size(inventory)` Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't @@ -3055,6 +3209,8 @@ __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) / __on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block __on_player_places_block(player, item_tuple, hand, block) // player have just placed the block. __on_player_interacts_with_entity(player, entity, hand) +__on_player_chooses_recipe(player, recipe, full_stack) +__on_player_switches_slot(player, from, to) __on_player_attacks_entity(player, entity) __on_player_takes_damage(player, amount, source, source_entity) __on_player_deals_damage(player, amount, entity) @@ -3185,7 +3341,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. - * `width` - line thickness, defaults to 2.0pt + * `line` - line thickness, defaults to 2.0pt * `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 @@ -3248,6 +3404,30 @@ Consult section about container operations in `Expression` to learn about possib Excapes all the special characters in the string or nbt tag and returns a string that can be stored in nbt directly as a string value. +### parse_nbt(tag) + +Converts NBT tag to a scarpet value, which you can navigate through much better. + +Converts: + - Compound tags into maps with string keys + - List tags into list values + - Numbers (Ints, Floats, Doubles, Longs) into a number + - Rest is converted to strings. + +### encode_nbt(expr, force?) + +Encodes value of the expression as an NBT tag. By default (or when `force` is false), it will only allow +to encode values that are guaranteed to return the same value when applied the resulting tag to `parse_nbt()`. +Supported types that can reliably convert back and forth to and from NBT values are: + - Maps with string keywords + - Lists of items of the same type (scarpet will take care of unifying value types if possible) + - Numbers (encoded as Ints -> Longs -> Doubles, as needed) + - Strings + +Other value types will only be converted to tags (including NBT tags) if `force` is true. They would require +extra treatment when loading them back from NBT, but using `force` true will always produce output / never +produce an exception. + ### `print(expr)` Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. @@ -3318,7 +3498,7 @@ With the specified `resource` in the scripts folder, of a specific `type`, write content, or deletes the resource. Resource is identified by a path to the file. -A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred based on the `type` of the file. diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 98021758..c5566f99 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -67,7 +67,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. - * `width` - line thickness, defaults to 2.0pt + * `line` - line thickness, defaults to 2.0pt * `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 @@ -130,6 +130,30 @@ Consult section about container operations in `Expression` to learn about possib Excapes all the special characters in the string or nbt tag and returns a string that can be stored in nbt directly as a string value. +### parse_nbt(tag) + +Converts NBT tag to a scarpet value, which you can navigate through much better. + +Converts: + - Compound tags into maps with string keys + - List tags into list values + - Numbers (Ints, Floats, Doubles, Longs) into a number + - Rest is converted to strings. + +### encode_nbt(expr, force?) + +Encodes value of the expression as an NBT tag. By default (or when `force` is false), it will only allow +to encode values that are guaranteed to return the same value when applied the resulting tag to `parse_nbt()`. +Supported types that can reliably convert back and forth to and from NBT values are: + - Maps with string keywords + - Lists of items of the same type (scarpet will take care of unifying value types if possible) + - Numbers (encoded as Ints -> Longs -> Doubles, as needed) + - Strings + +Other value types will only be converted to tags (including NBT tags) if `force` is true. They would require +extra treatment when loading them back from NBT, but using `force` true will always produce output / never +produce an exception. + ### `print(expr)` Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. @@ -200,7 +224,7 @@ With the specified `resource` in the scripts folder, of a specific `type`, write content, or deletes the resource. Resource is identified by a path to the file. -A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped +A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred based on the `type` of the file. @@ -368,7 +392,7 @@ Queries in-game statistics for certain values. Categories include: * `picked_up`: items picked up * `dropped`: items dropped * `killed`: mobs killed -* `killed_by`: blocks mined +* `killed_by`: mobs killed by * `custom`: various random stats For the options of `entry`, consult your statistics page, or give it a guess. diff --git a/docs/scarpet/api/BlockIterations.md b/docs/scarpet/api/BlockIterations.md index f60375c8..29d39cf3 100644 --- a/docs/scarpet/api/BlockIterations.md +++ b/docs/scarpet/api/BlockIterations.md @@ -2,7 +2,7 @@ These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. -### `scan(cx, cy, cz, dx, dy, dz, px?, py?, pz?, expr)`, `scan(center, range, lower_range?, expr)` +### `scan(center, range, lower_range?, expr)` Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in @@ -18,14 +18,15 @@ which would cause the expression not be evaluated for their boolean value. `scan` also handles `continue` and `break` statements, using `continue`'s return value to use in place of expression return value. `break` return value has no effect. -### `volume(x1, y1, z1, x2, y2, z2, expr)` +### `volume(from_pos, to_pos, expr)` Evaluates expression for each block in the area, the same as the `scan` function, but using two opposite corners of the rectangular cuboid. Any corners can be specified, its like you would do with `/fill` command. +You can use a position or three coordinates to specify, it doesn't matter. For return value and handling `break` and `continue` statements, see `scan` function above. -### `neighbours(x, y, z), neighbours(block), neighbours(l(x,y,z))` +### `neighbours(pos)` Returns the list of 6 neighbouring blocks to the argument. Commonly used with other loop functions like `for`. @@ -33,13 +34,18 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block -### `rect(cx, cy, cz, dx?, dy?, dz?, px?, py?, pz?)` +### `rect(centre, range?, positive_range?)` -returns an iterator, just like `range` function that iterates over rectangular cubarea of blocks. If only center -point is specified, it iterates over 27 blocks. If `d` arguments are specified, expands selection by the respective -number of blocks in each direction. If `p` arguments are specified, it uses `d` for negative offset, and `p` for positive. +Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center +point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective +number of blocks in each direction. If `positive_range` arguments are specified, + it uses `range` for negative offset, and `positive_range` for positive. -### `diamond(cx, cy, cz, radius?, height?)` +`centre` can be defined either as three coordinates, a list of three coords, or a block value. +`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center +as range instead of taking the values as is.` + +### `diamond(centre_pos, radius?, height?)` Iterates over a diamond like area of blocks. With no radius and height, its 7 blocks centered around the middle (block + neighbours). With a radius specified, it expands shape on x and z coords, and with a custom height, on y. diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 522faff2..5e638bde 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -36,11 +36,14 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block` All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the functions referenced below refer to either method of passing block position. -### `set(pos, block, property?, value?, ...)` +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. Second part, `block` is either block value as a result of `block()` function string value indicating the block name, -and optional `property - value` pairs for extra block properties. If `block` is specified only by name, then if a +and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to +be set for the target block. + +If `block` is specified only by name, then if a destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra properties. @@ -64,11 +67,15 @@ set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data +set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same ### `without_updates(expr)` -Evaluates subexpression without causing updates when blocks change in the world +Evaluates subexpression without causing updates when blocks change in the world. + +For synchronization sake, as well as from the fact that suppressed update can only happen within a tick, +the call to the `expr` is docked on the main server task. Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perlin noise generator. The following code causes a cascading effect as blocks placed on chunk borders will cause other chunks to get diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index a0524460..3a31838d 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -128,6 +128,10 @@ query(e,'type') => villager Returns a valid string to be used in commands to address an entity. Its UUID for all entities except player, where its their name. +## `query(e, 'persistence')` + +Returns if a mob has a persistence tag or not. Returns `null` for non-mob entities. +
 run('/kill ' + e~'command_name');
 
@@ -411,9 +415,15 @@ Sets the corresponding component of the motion vector. Adds a vector to the motion vector. Most realistic way to apply a force to an entity. -### `modify(e, 'custom_name'), modify(e, 'custom_name', name)` +### `modify(e, 'custom_name')`, `modify(e, 'custom_name', name)`, `modify(e, 'custom_name', name, visible)` -Sets the custom name of the entity. +Sets the custom name of the entity. Without arguments - clears current custom name. Optional visible affects +if the custom name is always visible, even through blocks. + +### `modify(e, 'persistence', bool?)` + +Sets the entity persistence tag to `true` (default) or `false`. Only affects mobs. Persistent mobs +don't despawn and don't count towards the mobcap. ### `modify(e, 'age', number)` @@ -532,6 +542,15 @@ Modifies directly player raw hunger components. Has no effect on non-players adds exhaustion value to the current player exhaustion level - that's the method you probably want to use to manipulate how much 'food' some action cost. +### `modify(e, 'nbt_merge', partial_tag)` + +Merges a partial tag into the entity data and reloads the entity from its updated tag. Cannot be applied to players + +### `modify(e, 'nbt', tag)` + +Reloads the entity from a supplied tag. Better get a valid entity tag, what can go wrong. Wonder what would happen if +transplant rabbit's brain into a villager. Cannot be applied to players. + ## Entity Events There is a number of events that happen to entities that you can attach your own code to in the form of event handlers. diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index 6a616f30..05b8c4cd 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -32,6 +32,8 @@ __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec) / __on_player_interacts_with_block(player, hand, block, face, hitvec) //right click on a block resulted in activation of said block __on_player_places_block(player, item_tuple, hand, block) // player have just placed the block. __on_player_interacts_with_entity(player, entity, hand) +__on_player_chooses_recipe(player, recipe, full_stack) +__on_player_switches_slot(player, from, to) __on_player_attacks_entity(player, entity) __on_player_takes_damage(player, amount, source, source_entity) __on_player_deals_damage(player, amount, entity) diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md index 5ef96c7a..52ad8340 100644 --- a/docs/scarpet/api/Inventories.md +++ b/docs/scarpet/api/Inventories.md @@ -38,6 +38,51 @@ item_category('ender_pearl') => misc item_category('stone') => building_blocks +### `recipe_data(item, type?)`, `recipe_data(recipe, type?)` + +returns all recipes matching either an `item`, or represent actual `recipe` name. In vanilla datapack, for all items +that have one recipe available, the recipe name is the same as the item name but if an item has multiple recipes, its +direct name can be different. + +Recipe type can take one of the following options: + * `'crafting'` - default, crafting table recipe + * `'smelting'` - furnace recipe + * `'blasting'` - blast furnace recipe + * `'smoking'` - smoker recipe + * `'campfire_cooking'` - campfire recipe + * `'stonecutting'` - stonecutter recipe + * `'smithing'` - smithing table (1.16+) + + The return value is a list of available recipes (even if there is only one recipe available). Each recipe contains of + an item triple of the crafting result, list of ingredients, each containing a list of possible variants of the + ingredients in this slot, as item triples, or `null` if its a shaped recipe and a given slot in the patterns is left + empty, and recipe specification as another list. Possible recipe specs is: + * `['shaped', width, height]` - shaped crafting. `width` and `height` can be 1, 2 or 3. + * `['shapeless']` - shapeless crafting + * `['smelting', duration, xp]` - smelting/cooking recipes + * `['cutting']` - stonecutter recipe + * `['special']` - special crafting recipe, typically not present in the crafting menu + * `['custom']` - other recipe types + +Note that ingredients are specified as tripes, with count and nbt information. Currently all recipes require always one +of the ingredients, and for some recipes, even if the nbt data for the ingredient is specified (e.g. `dispenser`), it +can accept items of any tags. + +Also note that some recipes leave some products in the crafting window, and these can be determined using + `crafting_remaining_item()` function + + Examples: +
+ recipe_data('iron_ingot_from_nuggets')
+ recipe_data('iron_ingot')
+ recipe_data('glass', 'smelting')
+ 
+ +### `crafting_remaining_item(item)` + +returns `null` if the item has no remaining item in the crafting window when used as a crafting ingredient, or an +item name that serves as a replacement after crafting is done. Currently it can only be buckets and glass bottles. + ### `inventory_size(inventory)` Returns the size of the inventory for the entity or block in question. Returns null if the block or entity don't diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md index 3064b89f..1dcd4fb1 100644 --- a/docs/scarpet/language/Containers.md +++ b/docs/scarpet/language/Containers.md @@ -117,26 +117,31 @@ tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[ ## List operations -### `l(values ...), l(iterator)` +### `[value, ...?]`,`[iterator]`,`l(value, ...?)`, `l(iterator)` Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all elements are variables, you coujld use it to return multiple results from one function call, if that -function returns a list of results with the same size as the `l` call uses. In case there is only one +function returns a list of results with the same size as the `[]` call uses. In case there is only one argument and it is an iterator (vanilla expression specification has `range`, but Minecraft API implements a bunch of them, like `diamond`), it will convert it to a proper list. Iterators can only be used in high order -functions, and are treated as empty lists, unless unrolled with `l` +functions, and are treated as empty lists, unless unrolled with `[]`. + +Internally, `[elem, ...]`(list syntax) and `l(elem, ...)`(function syntax) are equivalent. `[]` is simply translated to +`l()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `[]` +not using different kinds of brackets and not being proper operators. This means that `l(]` and `[)` are also valid +although not recommended as they will make your code far less readable.
-l(1,2,'foo') => [1, 2, foo]
-l() => [] (empty list)
-l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-l(1, 2) = l(3, 4) => Error: l is not a variable
-l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
-l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz)  => [3, 4, 5]
+l(1,2,'foo') <=> [1, 2, 'foo']
+l() <=> [] (empty list)
+[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+[1, 2] = [3, 4] => Error: l is not a variable
+[foo, bar] = [3, 4]; foo==3 && bar==4 => 1
+[foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz]  => [3, 4, 5]
 
-In the last example `l(min(foo, bar), baz)` creates a valid L-value, as min(foo, bar) finds the lower of the -variables (in this case `foo`) creating a valid assignable L-list of [foo, baz], and these values +In the last example `[min(foo, bar), baz]` creates a valid L-value, as `min(foo, bar)` finds the lower of the +variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values will be assigned new values ### `join(delim, list), join(delim, values ...)` @@ -210,29 +215,37 @@ range(5,10) => [5, 6, 7, 8, 9] range(20, 10, -2) => [20, 18, 16, 14, 12] -### `element(list, index)(deprecated)` - -Legacy support for older method that worked only on lists. Please use `get(...)` for equivalent support, or `.` -operator. Also previous unique behaviours with `put` on lists has been removed to support all type of -container types. - ## Map operations Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. Apart from container access functions, (`. , get, put, has, delete`), the following functions: -### `m(values ...), m(iterator), m(key_value_pairs)` +### `{values, ...}`,`{iterator}`,`{key -> value, ...}`,`m(values, ...)`, `m(iterator)`, `m(l(key, value), ...))` creates and initializes a map with supplied keys, and values. If the arguments contains a flat list, these are all treated as keys with no value, same goes with the iterator - creates a map that behaves like a set. If the arguments is a list of lists, they have to have two elements each, and then first is a key, and second, a value +In map creation context (directly inside `{}` or `m{}` call), `->` operator acts like a pair constructor for simpler +syntax providing key value pairs, so the invocation to `{foo -> bar, baz -> quux}` is equivalent to +`{l(foo, bar), l(baz, quux)}`, which is equivalent to somewhat older, but more traditional functional form of +`m(l(foo, bar),l(baz, quuz))`. + +Internally, `{?}`(list syntax) and `m(?)`(function syntax) are equivalent. `{}` is simply translated to +`m()` in the scarpet preprocessing stage. This means that internally the code has always expression syntax despite `{}` +not using different kinds of brackets and not being proper operators. This means that `m(}` and `{)` are also valid +although not recommended as they will make your code far less readable. + +When converting map value to string, `':'` is used as a key-value separator due to tentative compatibility with NBT +notation, meaning in simpler cases maps can be converted to NBT parsable string by calling `str()`. This however +does not guarantee a parsable output. To properly convert to NBT value, use `encode_nbt()`. +
-m(1,2,'foo') => {1: null, 2: null, foo: null}
-m() => {} (empty map)
-m(range(10)) => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
-m(l(1, 2), l(3, 4)) => {1: 2, 3: 4}
-reduce(range(10), put(_a, _, _*_); _a, m())
+{1, 2, 'foo'} => {1: null, 2: null, foo: null}
+m() <=> {} (empty map)
+{range(10)} => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
+m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
+reduce(range(10), put(_a, _, _*_); _a, {})
      => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
 
diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 03accaec..73d1de59 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -47,6 +47,8 @@ a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1,2 ### `Operator ->` +`->` operator has two uses - as a function definition operator and key-value initializer for maps. + To organize code better than a flat sequence of operations, one can define functions. Definition is correct if has the following form @@ -71,7 +73,17 @@ a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3] In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, -it needs to use `outer` function in function signature +it needs to use `outer` function in function signature. + +in map construction context (directly in `m()` or `{}`), the `->` operator has a different function by converting its +arguments to a tuple which is used by map constructor as a key-value pair: + +
+{ 'foo' -> 'bar' } => {l('foo', 'bar')}
+
+ +This means that it is not possible to define literally a set of inline function, however a set of functions can still +be created by adding elements to an empty set, and building it this way. ### `outer(arg)` diff --git a/docs/scarpet/language/Overview.md b/docs/scarpet/language/Overview.md index 0ad850f5..0cacbd36 100644 --- a/docs/scarpet/language/Overview.md +++ b/docs/scarpet/language/Overview.md @@ -1,15 +1,9 @@ -# Fundamental components of `scarpet` programming language (towards version 1.7). +# Fundamental components of `scarpet` programming language. Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide the ability to write custom programs to run within Minecraft and interact with the world. -The project was initially built based on the EvalEx project, however it now diverged -so far away from the original implementation, it would be hard to tell it without -this mention. EvalEx is a handy expression evaluator for Java, that allows to evaluate -simple mathematical and boolean expressions. For more information, -see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) - This specification is divided into two sections: this one is agnostic to any Minecraft related features and could function on its own, and CarpetExpression for Minecraft specific routines and world manipulation functions. @@ -277,3 +271,22 @@ check_foo_not_zero(); ... check_foo_not_zero() -> if(global_foo == 0, global_foo = 1) + +## Scarpet preprocessor + +There are several preprocessing operations applied to the source of your program to clean it up and prepare for +execution. Some of them will affect your code as it is reported via stack traces and function definition, and some +are applied only on the surface. + - stripping `//` comments (in file mode) + - replacing `$` with newlines (in command mode, modifies submitted code) + - removing extra semicolons that don't follow `;` use as a binary operator, allowing for lenient use of semicolons + - translating `{` into `m(`, `[` into `l(`, and `]` and `}` into `)` + +No further optimizations are currently applied to your code. + +## Mentions + +LR1 parser, tokenizer, and several built-in functions are built based on the EvalEx project. +EvalEx is a handy expression evaluator for Java, that allows to evaluate +simple mathematical and boolean expressions. EvalEx is distributed under MIT licence. +For more information, see: [EvalEx GitHub repository](https://github.com/uklimaschewski/EvalEx) diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index a1efa6d6..0e0eefc8 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -239,10 +239,13 @@ Thou shall not sample from noise changing seed frequently. Scarpet will keep tra used for sampling providing similar speed comparing to the default seed of `0`. In case the app engine uses more than 256 seeds at the same time, switching between them can get much more expensive. -### `print(expr)` +### `print(expr)`, `print(player, expr)` prints the value of the expression to chat. Passes the result of the argument to the output unchanged, -so `print`-statements can be weaved in code to debug programming issues +so `print`-statements can be weaved in code to debug programming issues. By default it uses the same communication +channels that most vanilla commands are using. + +In case player is directly specified, it only sends the message to that player, like `tell` command.
 print('foo') => results in foo, prints: foo
@@ -275,6 +278,52 @@ flip_my_world_upside_down();
 print(str('this took %d milliseconds',time()-start_time))
 
+### `unix_time()` + +Returns standard POSIX time as a number of milliseconds since the start of the epoch +(00:00 am and 0 seconds, 1 Jan 1970). +Unlike the previous function, this can be used to get exact time, but it varies from time zone to time zone. + +### `convert_date(milliseconds)` +### `convert_date(year, month, date, hours?, mins?, secs?)` +### `convert_date(l(year, month, date, hours?, mins?, secs?))` + +If called with a single argument, converts standard POSIX time to a list in the format: + +`l(year, month, date, hours, mins, secs, day_of_week, day_of_year, week_of_year)` + +eg: `convert_date(1592401346960) -> [2020, 6, 17, 10, 42, 26, 3, 169, 25]` + +Where the `6` stands for June, but `17` stands for 17th, `10` stands for 10am, +`42` stands for 42 minutes past the hour, and `26` stands for 26 seconds past the minute, +and `3` stands for Wednesday, `169` is the day of year, and `25` is a week of year. + +Run `convert_date(unix_time())` to get current time as list. + + +When called with a list, or with 3 or 6 arguments, returns standard POSIX time as a number of milliseconds since the + start of the epoch (1 Jan 1970), +using the time inputted into the function as opposed to the system time. + +Example editing: +
+date = convert_date(unix_time());
+
+months = l('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
+
+days = l('Mon','Tue','Wed','Thu','Fri','Sat','Sun');
+
+print(
+  str('Its %s, %d %s %d, %02d:%02d:%02d', 
+    days:(date:6-1), date:2, months:(date:1-1), date:0, date:3, date:4, date:5 
+  )
+)  
+
+ +This will give you a date: + +It is currently `hrs`:`mins` and `secs` seconds on the `date`th of `month`, `year` + ### `profile_expr(expression)` Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. diff --git a/docs/scarpet/language/VariablesAndConstants.md b/docs/scarpet/language/VariablesAndConstants.md index e3478327..f45f46b8 100644 --- a/docs/scarpet/language/VariablesAndConstants.md +++ b/docs/scarpet/language/VariablesAndConstants.md @@ -17,9 +17,9 @@ block positions. All variables starting with `_` are read-only, and cannot be de `scarpet` accepts numeric and string liters constants. Numbers look like `1, 2.5, -3e-7, 0xff,` and are internally represented as Java's `double` but `scarpet` will try to trim trailing zeros as much as possible so if you need to -use them as intergers, you can. Strings using single quoting, for multiple reasons, but primarily to allow for +use them as intergers, you can. Strings use single quoting, for multiple reasons, but primarily to allow for easier use of strings inside doubly quoted command arguments (when passing a script as a parameter of `/script fill` -for example, or when typing in jsons inside scarpet to feed back into a `/data merge` command for example. +for example), or when typing in jsons inside scarpet (to feed back into a `/data merge` command for example). Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 7e2a8d8a..2d2496c5 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -12,6 +12,7 @@ blocks_daylight
 blocks_movement
 brightness
 chunk_tickets
+crafting_remaining_item
 create_marker
 current_dimension
 day_time
@@ -67,6 +68,7 @@ power
 property
 query
 random_tick
+recipe_data
 rect
 reload_chunk
 remove_all_markers
diff --git a/docs/scarpet/resources/editors/idea/4.txt b/docs/scarpet/resources/editors/idea/4.txt
index faf87463..1f659f99 100644
--- a/docs/scarpet/resources/editors/idea/4.txt
+++ b/docs/scarpet/resources/editors/idea/4.txt
@@ -6,6 +6,7 @@ __on_player_attacks_entity
 __on_player_breaks_block
 __on_player_clicks_block
 __on_player_changes_dimension
+__on_player_chooses_recipe
 __on_player_deploys_elytra
 __on_player_drops_item
 __on_player_drops_stack
@@ -21,6 +22,7 @@ __on_player_starts_sneaking
 __on_player_starts_sprinting
 __on_player_stops_sneaking
 __on_player_stops_sprinting
+__on_player_switches_slot
 __on_player_uses_item
 __on_player_wakes_up
 __on_statistic
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 33416a05..ffb50e58 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -62,7 +62,9 @@
 
                 player spawn entity_id entity_list entity_area entity_selector query modify entity_event
 
-                stack_limit item_category inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
+                stack_limit item_category recipe_data crafting_remaining_item
+
+                inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
 
                 scan volume neighbours rect diamond
 
@@ -87,6 +89,7 @@
                 __on_player_breaks_block
                 __on_player_clicks_block
                 __on_player_changes_dimension
+                __on_player_chooses_recipe
                 __on_player_deploys_elytra
                 __on_player_drops_item
                 __on_player_drops_stack
@@ -102,6 +105,7 @@
                 __on_player_starts_sprinting
                 __on_player_stops_sneaking
                 __on_player_stops_sprinting
+                __on_player_switches_slot
                 __on_player_uses_item
                 __on_player_wakes_up
                 __on_statistic
diff --git a/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml b/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml
new file mode 100644
index 00000000..ffb50e58
--- /dev/null
+++ b/docs/scarpet/resources/editors/npp/scarpet_v1.7_notepad++.xml
@@ -0,0 +1,152 @@
+
+    
+        
+            
+            
+        
+        
+            00// 01 02 03 04
+            0x
+            
+            
+            
+            
+            
+            
+            ; + - * / % ^ && || > >= < <= == != = += -> <> ! ~ , :
+            
+            (
+            
+            )
+            
+            
+            
+            
+            
+            
+            
+                not fact rand
+                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
+                sqrt
+                max min abs round floor ceil
+                mandelbrot relu
+                perlin simplex
+
+                print return exit throw try
+
+                task task_count task_value task_join task_completed synchronize task_dock
+
+                system_variable_get system_variable_set
+
+                l join split slice sort sort_key range m keys values pairs copy
+
+                get put has delete
+
+                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
+            
+            
+                block pos pos_offset solid air liquid flammable
+                transparent opacity blocks_daylight emitted_light light block_light sky_light see_sky brightness
+                hardness blast_resistance top loaded suffocates power ticks_randomly update block_tick random_tick in_slime_chunk
+                set without_updates
+                blocks_movement block_sound material map_colour property block_properties 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
+                add_chunk_ticket
+
+                scoreboard scoreboard_add scoreboard_remove scoreboard_display
+
+                player spawn entity_id entity_list entity_area entity_selector query modify entity_event
+
+                stack_limit item_category recipe_data crafting_remaining_item
+
+                inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
+
+                scan volume neighbours rect diamond
+
+                format logger sound particle particle_line particle_rect
+
+                run save tick_time world_time day_time last_tick_times game_tick seed current_dimension in_dimension view_distance
+
+                plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
+
+                load_app_data store_app_data escape_nbt statistic
+            
+            euler pi null true false
+            x y z p
+            _ _i _a _x _y _z
+            global_
+            
+                __command
+                __config
+                __on_chunk_generated
+                __on_close
+                __on_player_attacks_entity
+                __on_player_breaks_block
+                __on_player_clicks_block
+                __on_player_changes_dimension
+                __on_player_chooses_recipe
+                __on_player_deploys_elytra
+                __on_player_drops_item
+                __on_player_drops_stack
+                __on_player_finishes_using_item
+                __on_player_interacts_with_block
+                __on_player_interacts_with_entity
+                __on_player_jumps
+                __on_player_places_block
+                __on_player_releases_item
+                __on_player_rides
+                __on_player_right_clicks_block
+                __on_player_starts_sneaking
+                __on_player_starts_sprinting
+                __on_player_stops_sneaking
+                __on_player_stops_sprinting
+                __on_player_switches_slot
+                __on_player_uses_item
+                __on_player_wakes_up
+                __on_statistic
+                __on_tick
+                __on_tick_ender
+                __on_tick_nether
+                __on_player_takes_damage
+                __on_player_deals_damage
+                __on_player_dies
+                __on_player_respawns
+                __on_player_disconnects
+                __on_player_connects
+            
+            $
+            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/gradle.properties b/gradle.properties
index 699591e3..5ef399ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -6,10 +6,10 @@ org.gradle.jvmargs=-Xmx1G
     # or better: https://modmuss50.me/fabric.html
 	minecraft_version=1.15.2
 	yarn_mappings=1.15.2+build.17
-	loader_version=0.8.7+build.201
+	loader_version=0.8.8+build.202
 
 # Mod Properties
-	mod_version = 1.3.29+v200611
+	mod_version = 1.4.0+v200623
 	maven_group = carpet
 	archives_base_name = fabric-carpet
 
diff --git a/src/main/java/carpet/CarpetExtension.java b/src/main/java/carpet/CarpetExtension.java
index 499c6c9e..6e630738 100644
--- a/src/main/java/carpet/CarpetExtension.java
+++ b/src/main/java/carpet/CarpetExtension.java
@@ -16,11 +16,18 @@ public interface CarpetExtension
     default void onGameStarted() {}
 
     /**
-     * Runs once per loaded world once the World files / gamerules etc are fully loaded
+     * Runs once per loaded world once the server / gamerules etc are fully loaded
+     * but before worlds are loaded
      * Can be loaded multiple times in SinglePlayer
      */
     default void onServerLoaded(MinecraftServer server) {}
 
+    /**
+     * Runs once per loaded world once the World files are fully loaded
+     * Can be loaded multiple times in SinglePlayer
+     */
+    default void onServerLoadedWorlds(MinecraftServer server) {}
+
     /**
      * Runs once per game tick, as a first thing in the tick
      */
@@ -55,4 +62,6 @@ public interface CarpetExtension
     default void registerLoggers() {}
 
     default Map canHasTranslations(String lang) { return null;}
+
+
 }
diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java
index 85b6d31e..57afe91e 100644
--- a/src/main/java/carpet/CarpetServer.java
+++ b/src/main/java/carpet/CarpetServer.java
@@ -58,12 +58,17 @@ public class CarpetServer // static for now - easier to handle all around the co
             e.onServerLoaded(server);
         });
         scriptServer = new CarpetScriptServer(server);
-        scriptServer.loadAllWorldScripts();
         MobAI.resetTrackers();
         LoggerRegistry.initLoggers();
         //TickSpeed.reset();
     }
 
+    public static void onServerLoadedWorlds(MinecraftServer minecraftServer)
+    {
+        extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer));
+        scriptServer.loadAllWorldScripts();
+    }
+
     public static void tick(MinecraftServer server)
     {
         TickSpeed.tick(server);
diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java
index c93402ce..675538db 100644
--- a/src/main/java/carpet/CarpetSettings.java
+++ b/src/main/java/carpet/CarpetSettings.java
@@ -34,7 +34,7 @@ import static carpet.settings.RuleCategory.CLIENT;
 @SuppressWarnings("CanBeFinal")
 public class CarpetSettings
 {
-    public static final String carpetVersion = "1.3.29+v200611";
+    public static final String carpetVersion = "1.4.0+v200623";
     public static final Logger LOG = LogManager.getLogger();
     public static boolean skipGenerationChecks = false;
     public static boolean impendingFillSkipUpdates = false;
diff --git a/src/main/java/carpet/fakes/IngredientInterface.java b/src/main/java/carpet/fakes/IngredientInterface.java
new file mode 100644
index 00000000..d910883e
--- /dev/null
+++ b/src/main/java/carpet/fakes/IngredientInterface.java
@@ -0,0 +1,11 @@
+package carpet.fakes;
+
+import net.minecraft.item.ItemStack;
+
+import java.util.Collection;
+import java.util.List;
+
+public interface IngredientInterface
+{
+    List> getRecipeStacks();
+}
diff --git a/src/main/java/carpet/fakes/MobEntityInterface.java b/src/main/java/carpet/fakes/MobEntityInterface.java
index 290e5304..9e3ae237 100644
--- a/src/main/java/carpet/fakes/MobEntityInterface.java
+++ b/src/main/java/carpet/fakes/MobEntityInterface.java
@@ -10,4 +10,6 @@ public interface MobEntityInterface
     GoalSelector getAI(boolean target);
 
     Map getTemporaryTasks();
+
+    void setPersistence(boolean what);
 }
diff --git a/src/main/java/carpet/fakes/RecipeManagerInterface.java b/src/main/java/carpet/fakes/RecipeManagerInterface.java
new file mode 100644
index 00000000..77f45d21
--- /dev/null
+++ b/src/main/java/carpet/fakes/RecipeManagerInterface.java
@@ -0,0 +1,12 @@
+package carpet.fakes;
+
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.util.Identifier;
+
+import java.util.List;
+
+public interface RecipeManagerInterface
+{
+    List> getAllMatching(RecipeType type, Identifier output);
+}
diff --git a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
index 16a41708..94bd965b 100644
--- a/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
+++ b/src/main/java/carpet/mixins/ClientPlayerEntity_clientCommandMixin.java
@@ -14,6 +14,11 @@ public class ClientPlayerEntity_clientCommandMixin
     @Inject(method = "sendChatMessage", at = @At("HEAD"))
     private void inspectMessage(String string, CallbackInfo ci)
     {
+        if (string.startsWith("/call"))
+        {
+            String command = string.substring(6);
+            CarpetClient.sendClientCommand(command);
+        }
         if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet())
         {
             ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this;
diff --git a/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java b/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java
new file mode 100644
index 00000000..f4b68577
--- /dev/null
+++ b/src/main/java/carpet/mixins/Ingredient_scarpetMixin.java
@@ -0,0 +1,26 @@
+package carpet.mixins;
+
+import carpet.fakes.IngredientInterface;
+import net.minecraft.item.ItemStack;
+import net.minecraft.recipe.Ingredient;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Mixin(Ingredient.class)
+public class Ingredient_scarpetMixin implements IngredientInterface
+{
+    @Shadow @Final private Ingredient.Entry[] entries;
+
+    @Override
+    public List> getRecipeStacks()
+    {
+        List> res = Arrays.stream(entries).map(Ingredient.Entry::getStacks).collect(Collectors.toList());
+        return res;
+    }
+}
diff --git a/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java b/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
index b11f9d8c..d1927c4e 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_coreMixin.java
@@ -31,13 +31,18 @@ public abstract class MinecraftServer_coreMixin
         CarpetServer.tick((MinecraftServer) (Object) this);
     }
 
-    // Dedicated server only
     @Inject(method = "loadWorld", at = @At("HEAD"))
     private void serverLoaded(String string_1, String string_2, long long_1, LevelGeneratorType levelGeneratorType_1, JsonElement jsonElement_1, CallbackInfo ci)
     {
         CarpetServer.onServerLoaded((MinecraftServer) (Object) this);
     }
 
+    @Inject(method = "loadWorld", at = @At("RETURN"))
+    private void serverLoadedWorlds(String string_1, String string_2, long long_1, LevelGeneratorType levelGeneratorType_1, JsonElement jsonElement_1, CallbackInfo ci)
+    {
+        CarpetServer.onServerLoadedWorlds((MinecraftServer) (Object) this);
+    }
+
     @Inject(method = "shutdown", at = @At("HEAD"))
     private void serverClosed(CallbackInfo ci)
     {
diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
index 62e07d55..b06f2105 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
@@ -39,7 +39,7 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
     {
         timeReference = field_4557 = Util.getMeasuringTimeMs();
         tick(isAhead);
-        runTask();
+        while(runTask()) {Thread.yield();}
     }
 
     @Inject(method = "tick", at = @At(
diff --git a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
index 9b695772..d3a0ff47 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_tickspeedMixin.java
@@ -4,6 +4,7 @@ import carpet.helpers.TickSpeed;
 import carpet.utils.CarpetProfiler;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.ServerTask;
+import net.minecraft.server.world.ServerWorld;
 import net.minecraft.util.Util;
 import net.minecraft.util.profiler.DisableableProfiler;
 import net.minecraft.util.thread.ReentrantThreadExecutor;
@@ -50,6 +51,8 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
 
     @Shadow private volatile boolean loading;
 
+    @Shadow public abstract Iterable getWorlds();
+
     CarpetProfiler.ProfilerToken currentSection;
 
     private float carpetMsptAccum = 0.0f;
@@ -120,16 +123,16 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
 
             this.profiler.startTick();
             this.profiler.push("tick");
-            this.tick(TickSpeed.time_warp_start_time != 0 ? ()->!hasRunningTasks() : this::shouldKeepTicking);
+            this.tick(TickSpeed.time_warp_start_time != 0 ? ()->true : this::shouldKeepTicking);
             this.profiler.swap("nextTickWait");
+            if (TickSpeed.time_warp_start_time != 0) // clearing all hanging tasks no matter what when warping
+            {
+                while(this.runEveryTask()) {Thread.yield();}
+            }
             this.field_19249 = true;
             this.field_19248 = Math.max(Util.getMeasuringTimeMs() + /*50L*/ msThisTick, this.timeReference);
-            if (TickSpeed.time_warp_start_time != 0)
-            {
-                runTasks();
-                runTasks(() -> !hasRunningTasks() );
-            }
-            else { this.method_16208(); }
+            // run all tasks (this will not do a lot when warping), but that's fine since we already run them
+            this.method_16208();
             this.profiler.pop();
             this.profiler.endTick();
             this.loading = true;
@@ -137,6 +140,23 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
 
     }
 
+
+    private boolean runEveryTask() {
+        if (super.runTask()) {
+            return true;
+        } else {
+            if (true) { // unconditionally this time
+                for(ServerWorld serverlevel : getWorlds()) {
+                    if (serverlevel.getChunkManager().executeQueuedTasks()) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+    }
+
     @Inject(method = "tick", at = @At(
             value = "INVOKE",
             target = "Lnet/minecraft/server/PlayerManager;saveAllPlayerData()V",
diff --git a/src/main/java/carpet/mixins/MobEntityMixin.java b/src/main/java/carpet/mixins/MobEntityMixin.java
index daa3cee4..2d8a5fff 100644
--- a/src/main/java/carpet/mixins/MobEntityMixin.java
+++ b/src/main/java/carpet/mixins/MobEntityMixin.java
@@ -16,6 +16,7 @@ public abstract class MobEntityMixin implements MobEntityInterface
 {
     @Shadow @Final protected GoalSelector targetSelector;
     @Shadow @Final protected GoalSelector goalSelector;
+    @Shadow private boolean persistent;
     public final Map temporaryTasks = new HashMap<>();
 
     @Override
@@ -30,5 +31,9 @@ public abstract class MobEntityMixin implements MobEntityInterface
         return temporaryTasks;
     }
 
-
+    @Override
+    public void setPersistence(boolean what)
+    {
+        persistent = what;
+    }
 }
diff --git a/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java b/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java
new file mode 100644
index 00000000..c31d4648
--- /dev/null
+++ b/src/main/java/carpet/mixins/RecipeManager_scarpetMixin.java
@@ -0,0 +1,32 @@
+package carpet.mixins;
+
+import carpet.fakes.RecipeManagerInterface;
+import com.google.common.collect.Lists;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeManager;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Mixin(RecipeManager.class)
+public class RecipeManager_scarpetMixin implements RecipeManagerInterface
+{
+
+    @Shadow private Map, Map>> recipes;
+
+    @Override
+    public List> getAllMatching(RecipeType type, Identifier output)
+    {
+        Map> typeRecipes = recipes.get(type);
+        if (typeRecipes.containsKey(output)) return Collections.singletonList(typeRecipes.get(output));
+        return Lists.newArrayList(typeRecipes.values().stream().filter(
+                r -> Registry.ITEM.getId(r.getOutput().getItem()).equals(output)).collect(Collectors.toList()));
+    }
+}
diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
index 6128944c..e5ca554a 100644
--- a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
+++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
@@ -1,6 +1,9 @@
 package carpet.mixins;
 
 import net.minecraft.item.ItemStack;
+import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket;
+import net.minecraft.network.packet.c2s.play.CraftRequestC2SPacket;
+import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket;
 import net.minecraft.server.network.ServerPlayNetworkHandler;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket;
@@ -29,10 +32,12 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_RELEASED_ITEM;
 import static carpet.script.CarpetEventServer.Event.PLAYER_RIDES;
 import static carpet.script.CarpetEventServer.Event.PLAYER_JUMPS;
 import static carpet.script.CarpetEventServer.Event.PLAYER_RIGHT_CLICKS_BLOCK;
+import static carpet.script.CarpetEventServer.Event.PLAYER_CHOOSES_RECIPE;
 import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SNEAKING;
 import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SPRINTING;
 import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SNEAKING;
 import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SPRINTING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_SWITCHES_SLOT;
 import static carpet.script.CarpetEventServer.Event.PLAYER_USES_ITEM;
 import static carpet.script.CarpetEventServer.Event.PLAYER_WAKES_UP;
 
@@ -219,4 +224,28 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
         //todo add hit and hand in the future
         PLAYER_ATTACKS_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), null);
     }
+
+    @Inject(method = "onButtonClick", at = @At("HEAD"))
+    private void onItemBeingPickedFromInventory(ButtonClickC2SPacket packet, CallbackInfo ci)
+    {
+        // crafts not int the crafting window
+        //CarpetSettings.LOG.error("Player clicks button "+packet.getButtonId());
+    }
+    @Inject(method = "onCraftRequest", at = @At("HEAD"))
+    private void onRecipeSelectedInRecipeManager(CraftRequestC2SPacket packet, CallbackInfo ci)
+    {
+        if (PLAYER_CHOOSES_RECIPE.isNeeded())
+        {
+            PLAYER_CHOOSES_RECIPE.onRecipeSelected(player, packet.getRecipe(), packet.shouldCraftAll());
+        }
+    }
+
+    @Inject(method = "onUpdateSelectedSlot", at = @At("HEAD"))
+    private void onUpdatedSelectedSLot(UpdateSelectedSlotC2SPacket packet, CallbackInfo ci)
+    {
+        if (PLAYER_SWITCHES_SLOT.isNeeded())
+        {
+            PLAYER_SWITCHES_SLOT.onSlotSwitch(player, player.inventory.selectedSlot, packet.getSelectedSlot());
+        }
+    }
 }
diff --git a/src/main/java/carpet/network/CarpetClient.java b/src/main/java/carpet/network/CarpetClient.java
index 7010eb31..47f74fd4 100644
--- a/src/main/java/carpet/network/CarpetClient.java
+++ b/src/main/java/carpet/network/CarpetClient.java
@@ -1,8 +1,13 @@
 package carpet.network;
 
 import carpet.CarpetServer;
+import carpet.CarpetSettings;
 import carpet.script.utils.ShapesRenderer;
 import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.Tag;
+import net.minecraft.text.Text;
 import net.minecraft.util.Identifier;
 
 public class CarpetClient
@@ -51,4 +56,26 @@ public class CarpetClient
     {
         return isServerCarpet;
     }
+
+    public static boolean sendClientCommand(String command)
+    {
+        if (!isServerCarpet && CarpetServer.minecraft_server == null) return false;
+        ClientNetworkHandler.clientCommand(command);
+        return true;
+    }
+
+    public static void onClientCommand(Tag t)
+    {
+        CarpetSettings.LOG.info("Server Response:");
+        CompoundTag tag = (CompoundTag)t;
+        CarpetSettings.LOG.info(" - id: "+tag.getString("id"));
+        CarpetSettings.LOG.info(" - code: "+tag.getInt("code"));
+        if (tag.contains("error")) CarpetSettings.LOG.warn(" - error: "+tag.getString("error"));
+        if (tag.contains("output"))
+        {
+            ListTag outputTag = (ListTag) tag.get("output");
+            for (int i = 0; i < outputTag.size(); i++)
+                CarpetSettings.LOG.info(" - response: " + Text.Serializer.fromJson(outputTag.getString(i)).getString());
+        }
+    }
 }
diff --git a/src/main/java/carpet/network/ClientNetworkHandler.java b/src/main/java/carpet/network/ClientNetworkHandler.java
index c65e2ac6..e105069d 100644
--- a/src/main/java/carpet/network/ClientNetworkHandler.java
+++ b/src/main/java/carpet/network/ClientNetworkHandler.java
@@ -44,6 +44,9 @@ public class ClientNetworkHandler
             if (CarpetClient.shapes != null)
                 CarpetClient.shapes.addShapes((ListTag) t);
         });
+        put("clientCommand", (p, t) -> {
+            CarpetClient.onClientCommand(t);
+        });
     }};
 
 
@@ -99,4 +102,17 @@ public class ClientNetworkHandler
                 CarpetSettings.LOG.error("Unknown carpet data: "+key);
         }
     }
+
+    public static void clientCommand(String command)
+    {
+        CompoundTag tag = new CompoundTag();
+        tag.putString("id", command);
+        tag.putString("command", command);
+        CompoundTag outer = new CompoundTag();
+        outer.put("clientCommand", tag);
+        CarpetClient.getPlayer().networkHandler.sendPacket(new CustomPayloadC2SPacket(
+                CarpetClient.CARPET_CHANNEL,
+                (new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.DATA).writeCompoundTag(outer)
+        ));
+    }
 }
diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java
index 6de0b77b..b5427e6f 100644
--- a/src/main/java/carpet/network/ServerNetworkHandler.java
+++ b/src/main/java/carpet/network/ServerNetworkHandler.java
@@ -6,21 +6,35 @@ import carpet.helpers.TickSpeed;
 import carpet.settings.ParsedRule;
 import io.netty.buffer.Unpooled;
 import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.StringTag;
 import net.minecraft.nbt.Tag;
 import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
+import net.minecraft.server.command.ServerCommandSource;
 import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.text.Text;
 import net.minecraft.util.PacketByteBuf;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class ServerNetworkHandler
 {
     private static Map remoteCarpetPlayers = new HashMap<>();
     private static Set validCarpetPlayers = new HashSet<>();
 
+    private static Map> dataHandlers = new HashMap>(){{
+        put("clientCommand", (p, t) -> {
+            handleClientCommand(p, (CompoundTag)t);
+        });
+    }};
+
     public static void handleData(PacketByteBuf data, ServerPlayerEntity player)
     {
         if (data != null)
@@ -28,8 +42,13 @@ public class ServerNetworkHandler
             int id = data.readVarInt();
             if (id == CarpetClient.HELLO)
                 onHello(player, data);
+            if (id == CarpetClient.DATA)
+                onClientData(player, data);
         }
     }
+
+
+
     public static void onPlayerJoin(ServerPlayerEntity playerEntity)
     {
         if (!playerEntity.networkHandler.connection.isLocal())
@@ -60,6 +79,67 @@ public class ServerNetworkHandler
         CarpetServer.settingsManager.getRules().forEach(data::withRule);
         playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() ));
     }
+
+    private static void handleClientCommand(ServerPlayerEntity player, CompoundTag commandData)
+    {
+        String command = commandData.getString("command");
+        String id = commandData.getString("id");
+        List output = new ArrayList<>();
+        String[] error = {null};
+        int resultCode = -1;
+        if (player.getServer() == null)
+        {
+            error[0] = "No Server";
+        }
+        else
+        {
+            resultCode = player.getServer().getCommandManager().execute(
+                    new ServerCommandSource(player, player.getPos(), player.getRotationClient(),
+                    player.world instanceof ServerWorld ? (ServerWorld) player.world : null,
+                    player.server.getPermissionLevel(player.getGameProfile()), player.getName().getString(), player.getDisplayName(),
+                    player.world.getServer(), player)
+                    {
+                        @Override
+                        public void sendError(Text message)
+                        {
+                            error[0] = message.getString();
+                        }
+                        @Override
+                        public void sendFeedback(Text message, boolean broadcastToOps)
+                        {
+                            output.add(message);
+                        }
+                    },
+                    command
+            );
+        };
+        CompoundTag result = new CompoundTag();
+        result.putString("id", id);
+        result.putInt("code", resultCode);
+        if (error[0] != null) result.putString("error", error[0]);
+        ListTag outputResult = new ListTag();
+        for (Text line: output) outputResult.add(StringTag.of(Text.Serializer.toJson(line)));
+        if (!output.isEmpty()) result.put("output", outputResult);
+        player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
+                CarpetClient.CARPET_CHANNEL,
+                DataBuilder.create().withCustomNbt("clientCommand", result).build()
+        ));
+        // run command plug to command output,
+    }
+
+
+    private static void onClientData(ServerPlayerEntity player, PacketByteBuf data)
+    {
+        CompoundTag compound = data.readCompoundTag();
+        if (compound == null) return;
+        for (String key: compound.getKeys())
+        {
+            if (dataHandlers.containsKey(key))
+                dataHandlers.get(key).accept(player, compound.get(key));
+            else
+                CarpetSettings.LOG.warn("Unknown carpet client data: "+key);
+        }
+    }
     
     public static void updateRuleWithConnectedClients(ParsedRule rule)
     {
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index 2eafcd6d..75171171 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -488,6 +488,37 @@ public class CarpetEventServer
                 handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
             }
         },
+        PLAYER_CHOOSES_RECIPE("player_chooses_recipe", 3, false)
+        {
+            @Override
+            public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack)
+            {
+                handler.call( () ->
+                {
+                    return Arrays.asList(
+                            ((c, t) -> new EntityValue(player)),
+                            ((c, t) -> new StringValue(NBTSerializableValue.nameFromRegistryId(recipe))),
+                            ((c, t) -> new NumericValue(fullStack))
+                    );
+                }, player::getCommandSource);
+            }
+        },
+        PLAYER_SWITCHES_SLOT("player_switches_slot", 3, false)
+        {
+            @Override
+            public void onSlotSwitch(ServerPlayerEntity player, int from, int to)
+            {
+                if (from == to) return; // initial slot update
+                handler.call( () ->
+                {
+                    return Arrays.asList(
+                            ((c, t) -> new EntityValue(player)),
+                            ((c, t) -> new NumericValue(from)),
+                            ((c, t) -> new NumericValue(to))
+                    );
+                }, player::getCommandSource);
+            }
+        },
         PLAYER_TAKES_DAMAGE("player_takes_damage", 4, false)
         {
             @Override
@@ -647,6 +678,8 @@ public class CarpetEventServer
         public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
         public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, DimensionType fromDim, DimensionType dimTo) {}
         public void onDamage(Entity target, float amount, DamageSource source) { }
+        public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {}
+        public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
 
 
         public void onWorldEvent(ServerWorld world, BlockPos pos) { }
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 7a244270..c3892905 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -2,8 +2,10 @@ package carpet.script;
 
 import carpet.CarpetServer;
 import carpet.fakes.ChunkTicketManagerInterface;
+import carpet.fakes.IngredientInterface;
 import carpet.fakes.MinecraftServerInterface;
 import carpet.fakes.BiomeArrayInterface;
+import carpet.fakes.RecipeManagerInterface;
 import carpet.fakes.ServerChunkManagerInterface;
 import carpet.fakes.ServerLightingProviderInterface;
 import carpet.fakes.StatTypeInterface;
@@ -81,6 +83,13 @@ import net.minecraft.nbt.StringTag;
 import net.minecraft.nbt.Tag;
 import net.minecraft.particle.ParticleEffect;
 import net.minecraft.predicate.entity.EntityPredicates;
+import net.minecraft.recipe.AbstractCookingRecipe;
+import net.minecraft.recipe.CuttingRecipe;
+import net.minecraft.recipe.Recipe;
+import net.minecraft.recipe.RecipeType;
+import net.minecraft.recipe.ShapedRecipe;
+import net.minecraft.recipe.ShapelessRecipe;
+import net.minecraft.recipe.SpecialCraftingRecipe;
 import net.minecraft.scoreboard.Scoreboard;
 import net.minecraft.scoreboard.ScoreboardCriterion;
 import net.minecraft.scoreboard.ScoreboardObjective;
@@ -97,6 +106,7 @@ import net.minecraft.stat.Stat;
 import net.minecraft.stat.StatType;
 import net.minecraft.state.StateManager;
 import net.minecraft.state.property.Property;
+import net.minecraft.structure.StructureFeatures;
 import net.minecraft.structure.StructurePiece;
 import net.minecraft.structure.StructureStart;
 import net.minecraft.text.LiteralText;
@@ -111,9 +121,11 @@ import net.minecraft.util.math.ChunkPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.util.math.EulerAngle;
 import net.minecraft.util.math.BlockBox;
+import net.minecraft.util.math.MathHelper;
 import net.minecraft.util.math.Vec3d;
 import net.minecraft.util.math.Vec3i;
 import net.minecraft.util.registry.Registry;
+import net.minecraft.world.gen.chunk.ChunkGenerator;
 import net.minecraft.world.gen.feature.Feature;
 import net.minecraft.world.gen.feature.StructureFeature;
 import net.minecraft.world.poi.PointOfInterest;
@@ -130,8 +142,10 @@ import net.minecraft.world.dimension.DimensionType;
 import net.minecraft.world.gen.ChunkRandom;
 import org.apache.commons.lang3.tuple.Pair;
 
+import java.util.Date;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -342,6 +356,18 @@ public class CarpetExpression
         return MapValue.wrap(ret);
     }
 
+    private static long lastSeed = -1;
+    private void BooYah(ChunkGenerator generator)
+    {
+        synchronized (generator)
+        {
+            if (generator.getSeed() != lastSeed)
+            {
+                StructureFeatures.STRONGHOLD.shouldStartAt(null, generator, null, 0, 0, null);
+                lastSeed = generator.getSeed();
+            }
+        }
+    }
     private void API_BlockManipulation()
     {
         this.expr.addLazyFunction("block", -1, (c, t, lv) ->
@@ -737,43 +763,76 @@ public class CarpetExpression
         this.expr.addLazyFunction("without_updates", 1, (c, t, lv) ->
         {
             boolean previous = CarpetSettings.impendingFillSkipUpdates;
-            try
+            if (previous) return lv.get(0);
+            Value [] result = new Value[]{Value.NULL};
+            ((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () ->
             {
-                CarpetSettings.impendingFillSkipUpdates = true;
-                Value ret = lv.get(0).evalValue(c, t);
-                return (cc, tt) -> ret;
-            }
-            finally
-            {
-                CarpetSettings.impendingFillSkipUpdates = previous;
-            }
+                try
+                {
+                    CarpetSettings.impendingFillSkipUpdates = true;
+                    result[0] = lv.get(0).evalValue(c, t);
+                }
+                finally
+                {
+                    CarpetSettings.impendingFillSkipUpdates = previous;
+                }
+            });
+            return (cc, tt) -> result[0];
         });
 
         this.expr.addLazyFunction("set", -1, (c, t, lv) ->
         {
             CarpetContext cc = (CarpetContext)c;
             ServerWorld world = cc.s.getWorld();
-            if (lv.size() < 2 || lv.size() % 2 == 1)
-                throw new InternalExpressionException("'set' should have at least 2 params and odd attributes");
             BlockArgument targetLocator = BlockArgument.findIn(cc, lv, 0);
             BlockArgument sourceLocator = BlockArgument.findIn(cc, lv, targetLocator.offset, true);
             BlockState sourceBlockState = sourceLocator.block.getBlockState();
             BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos());
-            if (sourceLocator.offset < lv.size())
+            CompoundTag data = null;
+            if (lv.size() > sourceLocator.offset)
             {
-                StateManager states = sourceBlockState.getBlock().getStateManager();
-                for (int i = sourceLocator.offset; i < lv.size(); i += 2)
+                List args = new ArrayList<>();
+                for (int i = sourceLocator.offset, m = lv.size(); i < m; i++)
                 {
-                    String paramString = lv.get(i).evalValue(c).getString();
+                    args.add(lv.get(i).evalValue(c));
+                }
+                if (args.get(0) instanceof ListValue)
+                {
+                    if (args.size() == 2)
+                    {
+                        Value dataValue = NBTSerializableValue.fromValue( args.get(1));
+                        if (dataValue instanceof NBTSerializableValue)
+                        {
+                            data = ((NBTSerializableValue) dataValue).getCompoundTag();
+                        }
+                    }
+                    args = ((ListValue) args.get(0)).getItems();
+                }
+                else
+                {
+                    if ((args.size() & 1) == 1)
+                    {
+                        Value dataValue = NBTSerializableValue.fromValue( args.get(args.size()-1));
+                        if (dataValue instanceof NBTSerializableValue)
+                        {
+                            data = ((NBTSerializableValue) dataValue).getCompoundTag();
+                        }
+                    }
+                }
+                StateManager states = sourceBlockState.getBlock().getStateManager();
+                for (int i = 0; i < args.size()-1; i += 2)
+                {
+                    String paramString = args.get(i).getString();
                     Property property = states.getProperty(paramString);
                     if (property == null)
                         throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString());
-                    String paramValue = lv.get(i + 1).evalValue(c).getString();
+                    String paramValue = args.get(i + 1).getString();
                     sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState);
                 }
             }
 
-            CompoundTag data = sourceLocator.block.getData();
+            if (data == null) data = sourceLocator.block.getData();
+            CompoundTag finalData = data;
 
             if (sourceBlockState == targetBlockState && data == null)
                 return (c_, t_) -> Value.FALSE;
@@ -783,12 +842,12 @@ public class CarpetExpression
             {
                 Clearable.clear(world.getBlockEntity(targetPos));
                 world.setBlockState(targetPos, finalSourceBlockState, 2);
-                if (data != null)
+                if (finalData != null)
                 {
                     BlockEntity be = world.getBlockEntity(targetPos);
                     if (be != null)
                     {
-                        CompoundTag destTag = data.copy();
+                        CompoundTag destTag = finalData.copy();
                         destTag.putInt("x", targetPos.getX());
                         destTag.putInt("y", targetPos.getY());
                         destTag.putInt("z", targetPos.getZ());
@@ -897,7 +956,7 @@ public class CarpetExpression
             Tag outtag = tool.getTag();
             if (outtag == null)
                 return LazyValue.TRUE;
-            Value ret = new NBTSerializableValue(outtag);
+            Value ret = new NBTSerializableValue(() -> outtag);
             return (_c, _t) -> ret;
 
         });
@@ -1096,6 +1155,9 @@ public class CarpetExpression
             BlockArgument locator = BlockArgument.findIn(cc, lv, 0);
 
             ServerWorld world = cc.s.getWorld();
+
+            BooYah(world.getChunkManager().getChunkGenerator());
+
             BlockPos pos = locator.block.getPos();
             StructureFeature structure = null;
             boolean needSize = false;
@@ -1385,13 +1447,14 @@ public class CarpetExpression
         });
 
         this.expr.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> {
+            if (lv.size()==0) throw new InternalExpressionException("'scoreboard_remove' requires at least one parameter");
             CarpetContext cc = (CarpetContext)c;
             Scoreboard scoreboard =  cc.s.getMinecraftServer().getScoreboard();
             String objectiveName = lv.get(0).evalValue(c).getString();
             ScoreboardObjective objective = scoreboard.getObjective(objectiveName);
             if (objective == null)
                 return LazyValue.FALSE;
-            if (lv.size() == 0)
+            if (lv.size() == 1)
             {
                 scoreboard.removeObjective(objective);
                 return LazyValue.TRUE;
@@ -1472,6 +1535,120 @@ public class CarpetExpression
             return (_c, _t) -> res;
         });
 
+        this.expr.addLazyFunction("recipe_data", -1, (c, t, lv) ->
+        {
+            CarpetContext cc = (CarpetContext)c;
+            if (lv.size() < 1) throw new InternalExpressionException("'recipe_data' requires at least one argument");
+            String recipeName = lv.get(0).evalValue(c).getString();
+            RecipeType type = RecipeType.CRAFTING;
+            if (lv.size() > 1)
+            {
+                String recipeType = lv.get(1).evalValue(c).getString();
+                try
+                {
+                    type = Registry.RECIPE_TYPE.get(new Identifier(recipeType));
+                }
+                catch (InvalidIdentifierException ignored)
+                {
+                    throw new InternalExpressionException("Unknown crafting category: " + recipeType);
+                }
+            }
+            List> recipes;
+            try
+            {
+                recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
+            }
+            catch (InvalidIdentifierException ignored)
+            {
+                return LazyValue.NULL;
+            }
+            if (recipes.isEmpty())
+                return LazyValue.NULL;
+            List recipesOutput = new ArrayList<>();
+            for (Recipe recipe: recipes)
+            {
+                ItemStack result = recipe.getOutput();
+                List ingredientValue = new ArrayList<>();
+                recipe.getPreviewInputs().forEach(
+                        ingredient ->
+                        {
+                            // I am flattening ingredient lists per slot.
+                            // consider recipe_data('wooden_sword','crafting') and ('iron_nugget', 'blasting') and notice difference
+                            // in depths of lists.
+                            List> stacks = ((IngredientInterface) (Object) ingredient).getRecipeStacks();
+                            if (stacks.isEmpty())
+                            {
+                                ingredientValue.add(Value.NULL);
+                            }
+                            else
+                            {
+                                List alternatives = new ArrayList<>();
+                                stacks.forEach(col -> col.stream().map(ListValue::fromItemStack).forEach(alternatives::add));
+                                ingredientValue.add(ListValue.wrap(alternatives));
+                            }
+                        }
+                );
+                Value recipeSpec;
+                if (recipe instanceof ShapedRecipe)
+                {
+                    recipeSpec = ListValue.of(
+                            new StringValue("shaped"),
+                            new NumericValue(((ShapedRecipe) recipe).getWidth()),
+                            new NumericValue(((ShapedRecipe) recipe).getHeight())
+                    );
+                }
+                else if (recipe instanceof ShapelessRecipe)
+                {
+                    recipeSpec = ListValue.of(new StringValue("shapeless"));
+                }
+                else if (recipe instanceof AbstractCookingRecipe)
+                {
+                    recipeSpec = ListValue.of(
+                            new StringValue("smelting"),
+                            new NumericValue(((AbstractCookingRecipe) recipe).getCookTime()),
+                            new NumericValue(((AbstractCookingRecipe) recipe).getExperience())
+                    );
+                }
+                else if (recipe instanceof CuttingRecipe)
+                {
+                    recipeSpec = ListValue.of(new StringValue("cutting"));
+                }
+                else if (recipe instanceof SpecialCraftingRecipe)
+                {
+                    recipeSpec = ListValue.of(new StringValue("special"));
+                }
+                else
+                {
+                    recipeSpec = ListValue.of(new StringValue("custom"));
+                }
+
+                recipesOutput.add(ListValue.of(ListValue.fromItemStack(result), ListValue.wrap(ingredientValue), recipeSpec));
+            }
+            Value ret = ListValue.wrap(recipesOutput);
+            return (_c, _t) -> ret;
+        });
+
+        this.expr.addLazyFunction("crafting_remaining_item", 1, (c, t, lv) ->
+        {
+            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);
+            }
+            if (!item.hasRecipeRemainder()) return LazyValue.NULL;
+            Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder())));
+            return (_c, _t ) -> ret;
+        });
+
+
         this.expr.addLazyFunction("inventory_size", -1, (c, t, lv) ->
         {
             CarpetContext cc = (CarpetContext) c;
@@ -1825,11 +2002,9 @@ public class CarpetExpression
                 Value nbt = lv.get(position.offset).evalValue(c);
                 NBTSerializableValue v = (nbt instanceof NBTSerializableValue) ? (NBTSerializableValue) nbt
                         : NBTSerializableValue.parseString(nbt.getString());
-                if (v != null)
-                {
-                    hasTag = true;
-                    tag = v.getCompoundTag();
-                }
+                hasTag = true;
+                tag = v.getCompoundTag();
+
             }
             tag.putString("id", entityId.toString());
             Vec3d vec3d = position.vec;
@@ -2097,21 +2272,28 @@ public class CarpetExpression
             return (c_, t_) -> new NumericValue(finalSCount);
         });
 
-        this.expr.addLazyFunction("volume", 7, (c, t, lv) ->
+        this.expr.addLazyFunction("volume", -1, (c, t, lv) ->
         {
-            int xi = (int) NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
-            int yi = (int) NumericValue.asNumber(lv.get(1).evalValue(c)).getLong();
-            int zi = (int) NumericValue.asNumber(lv.get(2).evalValue(c)).getLong();
-            int xj = (int) NumericValue.asNumber(lv.get(3).evalValue(c)).getLong();
-            int yj = (int) NumericValue.asNumber(lv.get(4).evalValue(c)).getLong();
-            int zj = (int) NumericValue.asNumber(lv.get(5).evalValue(c)).getLong();
-            int minx = min(xi, xj);
-            int miny = min(yi, yj);
-            int minz = min(zi, zj);
-            int maxx = max(xi, xj);
-            int maxy = max(yi, yj);
-            int maxz = max(zi, zj);
-            LazyValue expr = lv.get(6);
+            CarpetContext cc = (CarpetContext)c;
+
+            BlockArgument pos1Locator = BlockArgument.findIn(cc, lv, 0);
+            BlockArgument pos2Locator = BlockArgument.findIn(cc, lv, pos1Locator.offset);
+            BlockPos pos1 = pos1Locator.block.getPos();
+            BlockPos pos2 = pos2Locator.block.getPos();
+
+            int x1 = pos1.getX();
+            int y1 = pos1.getY();
+            int z1 = pos1.getZ();
+            int x2 = pos2.getX();
+            int y2 = pos2.getY();
+            int z2 = pos2.getZ();
+            int minx = min(x1, x2);
+            int miny = min(y1, y2);
+            int minz = min(z1, z2);
+            int maxx = max(x1, x2);
+            int maxy = max(y1, y2);
+            int maxz = max(z1, z2);
+            LazyValue expr = lv.get(pos2Locator.offset);
 
             //saving outer scope
             LazyValue _x = c.getVariable("_x");
@@ -2181,57 +2363,63 @@ public class CarpetExpression
 
         this.expr.addLazyFunction("rect", -1, (c, t, lv)->
         {
-            if (lv.size() != 3 && lv.size() != 6 && lv.size() != 9)
+            CarpetContext cc = (CarpetContext) c;
+            int cx, cy, cz;
+            int sminx, sminy, sminz;
+            int smaxx, smaxy, smaxz;
+            BlockArgument cposLocator = BlockArgument.findIn(cc, lv, 0);
+            BlockPos cpos = cposLocator.block.getPos();
+            cx = cpos.getX();
+            cy = cpos.getY();
+            cz = cpos.getZ();
+            if (lv.size() > cposLocator.offset)
             {
-                throw new InternalExpressionException("Rectangular region should be specified with 3, 6, or 9 coordinates");
-            }
-            int cx;
-            int cy;
-            int cz;
-            int sminx;
-            int sminy;
-            int sminz;
-            int smaxx;
-            int smaxy;
-            int smaxz;
-            try
-            {
-                cx = (int)((NumericValue) lv.get(0).evalValue(c)).getLong();
-                cy = (int)((NumericValue) lv.get(1).evalValue(c)).getLong();
-                cz = (int)((NumericValue) lv.get(2).evalValue(c)).getLong();
-                if (lv.size()==3) // only done this way because of stupid Java lambda final reqs
+                Vector3Argument diffLocator = Vector3Argument.findIn(cc, lv, cposLocator.offset);
+                if (diffLocator.fromBlock)
                 {
-                    sminx = 1;
-                    sminy = 1;
-                    sminz = 1;
-                    smaxx = 1;
-                    smaxy = 1;
-                    smaxz = 1;
+                    sminx = MathHelper.floor(abs(diffLocator.vec.x - cx));
+                    sminy = MathHelper.floor(abs(diffLocator.vec.y - cx));
+                    sminz = MathHelper.floor(abs(diffLocator.vec.z - cx));
                 }
-                else if (lv.size()==6)
+                else
+                {
+                    sminx = MathHelper.floor(abs(diffLocator.vec.x));
+                    sminy = MathHelper.floor(abs(diffLocator.vec.y));
+                    sminz = MathHelper.floor(abs(diffLocator.vec.z));
+                }
+                if (lv.size() > diffLocator.offset)
+                {
+                    Vector3Argument posDiff = Vector3Argument.findIn(cc, lv, diffLocator.offset);
+                    if (posDiff.fromBlock)
+                    {
+                        smaxx = MathHelper.floor(abs(posDiff.vec.x - cx));
+                        smaxy = MathHelper.floor(abs(posDiff.vec.y - cx));
+                        smaxz = MathHelper.floor(abs(posDiff.vec.z - cx));
+                    }
+                    else
+                    {
+                        smaxx = MathHelper.floor(abs(posDiff.vec.x));
+                        smaxy = MathHelper.floor(abs(posDiff.vec.y));
+                        smaxz = MathHelper.floor(abs(posDiff.vec.z));
+                    }
+                }
+                else
                 {
-                    sminx = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong();
-                    sminy = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong();
-                    sminz = (int) ((NumericValue) lv.get(5).evalValue(c)).getLong();
                     smaxx = sminx;
                     smaxy = sminy;
                     smaxz = sminz;
                 }
-                else // size == 9
-                {
-                    sminx = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong();
-                    sminy = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong();
-                    sminz = (int) ((NumericValue) lv.get(5).evalValue(c)).getLong();
-                    smaxx = (int)((NumericValue) lv.get(6).evalValue(c)).getLong();
-                    smaxy = (int)((NumericValue) lv.get(7).evalValue(c)).getLong();
-                    smaxz = (int)((NumericValue) lv.get(8).evalValue(c)).getLong();
-                }
             }
-            catch (ClassCastException exc)
+            else
             {
-                throw new InternalExpressionException("Attempted to pass a non-number to rect");
+                sminx = 1;
+                sminy = 1;
+                sminz = 1;
+                smaxx = 1;
+                smaxy = 1;
+                smaxz = 1;
             }
-            CarpetContext cc = (CarpetContext)c;
+
             return (c_, t_) -> new LazyListValue()
             {
                 final int minx = cx-sminx;
@@ -2240,6 +2428,7 @@ public class CarpetExpression
                 final int maxx = cx+smaxx;
                 final int maxy = cy+smaxy;
                 final int maxz = cz+smaxz;
+
                 int x;
                 int y;
                 int z;
@@ -2299,10 +2488,9 @@ public class CarpetExpression
         this.expr.addLazyFunction("diamond", -1, (c, t, lv)->
         {
             CarpetContext cc = (CarpetContext)c;
-            if (lv.size() != 3 && lv.size() != 4 && lv.size() != 5)
-            {
-                throw new InternalExpressionException("'diamond' region should be specified with 3 to 5 coordinates");
-            }
+
+            BlockArgument cposLocator=BlockArgument.findIn((CarpetContext)c,lv,0);
+            BlockPos cpos = cposLocator.block.getPos();
 
             int cx;
             int cy;
@@ -2311,10 +2499,11 @@ public class CarpetExpression
             int height;
             try
             {
-                cx = (int)((NumericValue) lv.get(0).evalValue(c)).getLong();
-                cy = (int)((NumericValue) lv.get(1).evalValue(c)).getLong();
-                cz = (int)((NumericValue) lv.get(2).evalValue(c)).getLong();
-                if (lv.size()==3)
+                cx = cpos.getX();
+                cy = cpos.getY();
+                cz = cpos.getZ();
+
+                if (lv.size()==cposLocator.offset)
                 {
                     Value retval = ListValue.of(
                             BlockValue.fromCoords(cc, cx, cy-1, cz),
@@ -2327,20 +2516,22 @@ public class CarpetExpression
                     );
                     return (_c, _t ) -> retval;
                 }
-                else if (lv.size()==4)
+                else if (lv.size()==1+cposLocator.offset)
                 {
-                    width = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong();
+                    width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong();
                     height = 0;
                 }
-                else // size == 5
+                else if(lv.size()==2+cposLocator.offset)
                 {
-                    width = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong();
-                    height = (int) ((NumericValue) lv.get(4).evalValue(c)).getLong();
+                    width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong();
+                    height = (int) ((NumericValue) lv.get(cposLocator.offset+1).evalValue(c)).getLong();
+                } else{
+                    throw new InternalExpressionException("Incorrect number of arguments for 'diamond'");
                 }
             }
             catch (ClassCastException exc)
             {
-                throw new InternalExpressionException("Attempted to pass a non-number to diamond");
+                throw new InternalExpressionException("Attempted to pass a non-number to 'diamond'");
             }
             if (height == 0)
             {
@@ -2480,7 +2671,7 @@ public class CarpetExpression
     public static String recognizeResource(Value value)
     {
         String origfile = value.getString();
-        String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9/]", "");
+        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())
         {
@@ -2756,12 +2947,7 @@ public class CarpetExpression
         });
 
         this.expr.addLazyFunction("nbt", 1, (c, t, lv) -> {
-            Value v = lv.get(0).evalValue(c);
-            if (v instanceof NBTSerializableValue)
-                return (cc, tt) -> v;
-            Value ret = NBTSerializableValue.parseString(v.getString());
-            if (ret == null)
-                return LazyValue.NULL;
+            Value ret = NBTSerializableValue.fromValue(lv.get(0).evalValue(c));
             return (cc, tt) -> ret;
         });
 
@@ -2772,11 +2958,50 @@ public class CarpetExpression
             return (cc, tt) -> ret;
         });
 
+        this.expr.addLazyFunction("parse_nbt", 1, (c, t, lv) -> {
+            Value v = lv.get(0).evalValue(c);
+            if (v instanceof NBTSerializableValue)
+            {
+                Value parsed = ((NBTSerializableValue) v).toValue();
+                return (cc, tt) -> parsed;
+            }
+            NBTSerializableValue ret = NBTSerializableValue.parseString(v.getString());
+            if (ret == null)
+                return LazyValue.NULL;
+            Value parsed = ret.toValue();
+            return (cc, tt) -> parsed;
+        });
+
+        this.expr.addLazyFunction("encode_nbt", -1, (c, t, lv) -> {
+            int argSize = lv.size();
+            if (argSize==0 || argSize > 2) throw new InternalExpressionException("'encode_nbt' requires 1 or 2 parameters");
+            Value v = lv.get(0).evalValue(c);
+            boolean force = (argSize > 1) && lv.get(1).evalValue(c).getBoolean();
+            Tag tag;
+            try
+            {
+                tag = v.toTag(force);
+            }
+            catch (NBTSerializableValue.IncompatibleTypeException ignored)
+            {
+                throw new InternalExpressionException("cannot reliably encode to a tag the value of '"+ignored.val.getPrettyString()+"'");
+            }
+            Value tagValue = new NBTSerializableValue(tag);
+            return (cc, tt) -> tagValue;
+        });
+
         //"overridden" native call that prints to stderr
-        this.expr.addLazyFunction("print", 1, (c, t, lv) ->
+        this.expr.addLazyFunction("print", -1, (c, t, lv) ->
         {
+            if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments");
             ServerCommandSource s = ((CarpetContext)c).s;
             Value res = lv.get(0).evalValue(c);
+            if (lv.size() == 2)
+            {
+                ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res);
+                s = player.getCommandSource();
+                res = lv.get(1).evalValue(c);
+            }
             if (res instanceof FormattedTextValue)
             {
                 s.sendFeedback(((FormattedTextValue) res).getText(), false);
@@ -2792,7 +3017,8 @@ public class CarpetExpression
                     Messenger.m(s, "w " + res.getString());
                 }
             }
-            return (_c, _t) -> res; // pass through for variables
+            Value finalRes = res;
+            return (_c, _t) -> finalRes; // pass through for variables
         });
 
         this.expr.addLazyFunction("format", -1, (c, t, lv) -> {
@@ -2862,9 +3088,14 @@ public class CarpetExpression
             BlockPos target = ((CarpetContext)c).origin;
             Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D);
             ServerCommandSource s = ((CarpetContext)c).s;
-            Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute(
-                    s.withPosition(posf).withSilent().withLevel(CarpetSettings.runPermissionLevel), lv.get(0).evalValue(c).getString()));
-            return (c_, t_) -> retval;
+            try
+            {
+                Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute(
+                        s.withPosition(posf).withSilent().withLevel(CarpetSettings.runPermissionLevel), lv.get(0).evalValue(c).getString()));
+                return (c_, t_) -> retval;
+            }
+            catch (Exception ignored) {}
+            return LazyValue.NULL;
         });
 
         this.expr.addLazyFunction("save", 0, (c, t, lv) -> {
@@ -2945,7 +3176,7 @@ public class CarpetExpression
 
         this.expr.addLazyFunction("seed", -1, (c, t, lv) -> {
             ServerCommandSource s = ((CarpetContext)c).s;
-            Value ret = new NumericValue(s.getWorld().getSeed());
+            Value ret = new StringValue(Long.toString(s.getWorld().getSeed()));
             return (cc, tt) -> ret;
         });
 
@@ -3013,10 +3244,12 @@ public class CarpetExpression
                     default:
                         throw new InternalExpressionException("Incorrect dimension string: "+dimString);
                 }
-            }
-            ((CarpetContext) c).s = innerSource;
-            Value retval = lv.get(1).evalValue(c);
-            ((CarpetContext) c).s = outerSource;
+            };
+            if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1);
+            Context newCtx = c.recreate();
+            ((CarpetContext) newCtx).s = innerSource;
+            newCtx.variables = c.variables;
+            Value retval = lv.get(1).evalValue(newCtx);
             return (cc, tt) -> retval;
         });
 
diff --git a/src/main/java/carpet/script/Context.java b/src/main/java/carpet/script/Context.java
index bd020493..50548fb3 100644
--- a/src/main/java/carpet/script/Context.java
+++ b/src/main/java/carpet/script/Context.java
@@ -18,8 +18,9 @@ public class Context
     public static final int SIGNATURE = 7;
     public static final int LOCALIZATION = 8;
     public static final int LVALUE = 9;
+    public static final int MAPDEF = 10;
 
-    public final Map variables = new HashMap<>();
+    public Map variables = new HashMap<>();
 
     public final ScriptHost host;
 
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index a53f4eb5..9031d021 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -43,7 +43,9 @@ import org.apache.commons.lang3.text.WordUtils;
 import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collections;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.IllegalFormatException;
 import java.util.Iterator;
@@ -490,6 +492,11 @@ public class Expression
         //assigns const procedure to the lhs, returning its previous value
         addLazyBinaryOperatorWithDelegation("->", precedence.get("def->"), false, (c, type, e, t, lv1, lv2) ->
         {
+            if (type == Context.MAPDEF)
+            {
+                Value result = ListValue.of(lv1.evalValue(c), lv2.evalValue(c));
+                return (cc, tt) -> result;
+            }
             Value v1 = lv1.evalValue(c, Context.SIGNATURE);
             if (!(v1 instanceof FunctionSignatureValue))
                 throw new InternalExpressionException("'->' operator requires a function signature on the LHS");
@@ -1333,11 +1340,18 @@ public class Expression
             return LazyListValue.range(from, to, step);
         });
 
-        addFunction("m", lv ->
+        addLazyFunction("m", -1, (c, t, llv) ->
         {
+            List lv = new ArrayList<>();
+            for (LazyValue lazyParam : llv) {
+                lv.add(lazyParam.evalValue(c, Context.MAPDEF)); // none type default by design
+            }
+            Value ret;
             if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
-                return new MapValue(((LazyListValue) lv.get(0)).unroll());
-            return new MapValue(lv);
+                ret = new MapValue(((LazyListValue) lv.get(0)).unroll());
+            else
+                ret = new MapValue(lv);
+            return (cc, tt) -> ret;
         });
 
         addUnaryFunction("keys", v -> {
@@ -1449,20 +1463,6 @@ public class Expression
             return (cc, tt) -> ret;
         });
 
-        //Deprecated, use "get" instead, or . operator
-        addBinaryFunction("element", (v1, v2) ->
-        {
-            if (!(v1 instanceof ListValue))
-                throw new InternalExpressionException("First argument of 'get' should be a list");
-            List items = ((ListValue)v1).getItems();
-            long index = NumericValue.asNumber(v2).getLong();
-            int numitems = items.size();
-            long range = abs(index)/numitems;
-            index += (range+2)*numitems;
-            index = index % numitems;
-            return items.get((int)index);
-        });
-
         addLazyFunction("put", -1, (c, t, lv) ->
         {
             if(lv.size()<2)
@@ -1790,6 +1790,66 @@ public class Expression
             return (cc, tt) -> time;
         });
 
+        addLazyFunction("unix_time", 0, (c, t, lv) ->
+        {
+            Value time = new NumericValue(System.currentTimeMillis());
+            return (cc, tt) -> time;
+        });
+
+        addFunction("convert_date", lv ->
+        {
+            int argsize = lv.size();
+            if (lv.size() == 0) throw new InternalExpressionException("'convert_date' requires at least one parameter");
+            Value value = lv.get(0);
+            if (argsize == 1 && !(value instanceof ListValue))
+            {
+                Calendar cal = new GregorianCalendar(Locale.ROOT);
+                cal.setTimeInMillis(NumericValue.asNumber(value, "timestamp").getLong());
+                int weekday = cal.get(Calendar.DAY_OF_WEEK)-1;
+                if (weekday == 0) weekday = 7;
+                Value retVal = ListValue.ofNums(
+                        cal.get(Calendar.YEAR),
+                        cal.get(Calendar.MONTH)+1,
+                        cal.get(Calendar.DAY_OF_MONTH),
+                        cal.get(Calendar.HOUR_OF_DAY),
+                        cal.get(Calendar.MINUTE),
+                        cal.get(Calendar.SECOND),
+                        weekday,
+                        cal.get(Calendar.DAY_OF_YEAR),
+                        cal.get(Calendar.WEEK_OF_YEAR)
+                );
+                return retVal;
+            }
+            else if(value instanceof ListValue)
+            {
+                lv = ((ListValue) value).getItems();
+                argsize = lv.size();
+            }
+            Calendar cal = new GregorianCalendar(0, 0, 0, 0, 0, 0);
+
+            if (argsize == 3)
+            {
+                cal.set(
+                        NumericValue.asNumber(lv.get(0)).getInt(),
+                        NumericValue.asNumber(lv.get(1)).getInt()-1,
+                        NumericValue.asNumber(lv.get(2)).getInt()
+                );
+            }
+            else if (argsize == 6)
+            {
+                cal.set(
+                        NumericValue.asNumber(lv.get(0)).getInt(),
+                        NumericValue.asNumber(lv.get(1)).getInt()-1,
+                        NumericValue.asNumber(lv.get(2)).getInt(),
+                        NumericValue.asNumber(lv.get(3)).getInt(),
+                        NumericValue.asNumber(lv.get(4)).getInt(),
+                        NumericValue.asNumber(lv.get(5)).getInt()
+                );
+            }
+            else throw new InternalExpressionException("Date conversion requires 3 arguments for Dates or 6 arguments, for time");
+            return new NumericValue(cal.getTimeInMillis());
+        });
+
         addLazyFunction("profile_expr", 1, (c, t, lv) ->
         {
             LazyValue lazy = lv.get(0);
@@ -2000,7 +2060,7 @@ public class Expression
 
         Tokenizer tokenizer = new Tokenizer(c, this, expression, allowComments, allowNewlineSubstitutions);
         // stripping lousy but acceptable semicolons
-        List cleanedTokens = tokenizer.fixSemicolons();
+        List cleanedTokens = tokenizer.postProcess();
 
         Tokenizer.Token lastFunction = null;
         Tokenizer.Token previousToken = null;
diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java
index ef4c0d5e..61668abc 100644
--- a/src/main/java/carpet/script/Tokenizer.java
+++ b/src/main/java/carpet/script/Tokenizer.java
@@ -43,7 +43,7 @@ public class Tokenizer implements Iterator
         this.newLinesMarkers = allowNewLineMakers;
     }
 
-    public List fixSemicolons()
+    public List postProcess()
     {
         Iterable iterable = () -> this;
         List originalTokens = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
@@ -52,6 +52,9 @@ public class Tokenizer implements Iterator
         while (originalTokens.size() > 0)
         {
             Token current = originalTokens.remove(originalTokens.size()-1);
+            if (current.type == Token.TokenType.MARKER && current.surface.startsWith("//"))
+                continue;
+                // skipping comments
             if (!isSemicolon(current)
                     || (last != null && last.type != Token.TokenType.CLOSE_PAREN && last.type != Token.TokenType.COMMA && !isSemicolon(last)))
             {
@@ -60,9 +63,27 @@ public class Tokenizer implements Iterator
                     current.surface = ";";
                     current.type = Token.TokenType.OPERATOR;
                 }
+                if (current.type == Token.TokenType.MARKER)
+                {
+                    // dealing with tokens in reversed order
+                    if ("{".equals(current.surface))
+                    {
+                        cleanedTokens.add(current.morphedInto(Token.TokenType.OPEN_PAREN, "("));
+                        current.morph(Token.TokenType.FUNCTION, "m");
+                    }
+                    else if ("[".equals(current.surface))
+                    {
+                        cleanedTokens.add(current.morphedInto(Token.TokenType.OPEN_PAREN, "("));
+                        current.morph(Token.TokenType.FUNCTION, "l");
+                    }
+                    else if ("}".equals(current.surface) || "]".equals(current.surface))
+                    {
+                        current.morph(Token.TokenType.CLOSE_PAREN, ")");
+                    }
+                }
                 cleanedTokens.add(current);
             }
-            if (current.type != Token.TokenType.MARKER)
+            if (!(current.type == Token.TokenType.MARKER && current.surface.equals("$")))
                 last = current;
         }
         Collections.reverse(cleanedTokens);
@@ -242,7 +263,8 @@ public class Tokenizer implements Iterator
             }
             token.type = ch == '(' ? Token.TokenType.FUNCTION : Token.TokenType.VARIABLE;
         }
-        else if (ch == '(' || ch == ')' || ch == ',')
+        else if (ch == '(' || ch == ')' || ch == ',' ||
+                ch == '{' || ch == '}' || ch == '[' || ch == ']')
         {
             if (ch == '(')
             {
@@ -252,17 +274,21 @@ public class Tokenizer implements Iterator
             {
                 token.type = Token.TokenType.CLOSE_PAREN;
             }
-            else
+            else if (ch == ',')
             {
                 token.type = Token.TokenType.COMMA;
             }
+            else
+            {
+                token.type = Token.TokenType.MARKER;
+            }
             token.append(ch);
             pos++;
             linepos++;
 
             if (expression != null && context != null && previousToken != null &&
                     previousToken.type == Token.TokenType.OPERATOR &&
-                    (ch == ')' || ch == ',') &&
+                    (ch == ')' || ch == ',' || ch == ']' || ch == '}') &&
                     !previousToken.surface.equalsIgnoreCase(";")
             )
                 throw new ExpressionException(context, this.expression, previousToken,
@@ -326,7 +352,9 @@ public class Tokenizer implements Iterator
             }
 
             if (previousToken == null || previousToken.type == Token.TokenType.OPERATOR
-                    || previousToken.type == Token.TokenType.OPEN_PAREN || previousToken.type == Token.TokenType.COMMA)
+                    || previousToken.type == Token.TokenType.OPEN_PAREN || previousToken.type == Token.TokenType.COMMA
+                    || (previousToken.type == Token.TokenType.MARKER && ( previousToken.surface.equals("{") || previousToken.surface.equals("[") ) )
+            )
             {
                 token.surface += "u";
                 token.type = Token.TokenType.UNARY_OPERATOR;
@@ -376,6 +404,22 @@ public class Tokenizer implements Iterator
         public int pos;
         public int linepos;
         public int lineno;
+        public Token morphedInto(TokenType newType, String newSurface)
+        {
+            Token created = new Token();
+            created.surface = newSurface;
+            created.type = newType;
+            created.pos = pos;
+            created.linepos = linepos;
+            created.lineno= lineno;
+            return created;
+        }
+
+        public void morph(TokenType type, String s)
+        {
+            this.type = type;
+            this.surface = s;
+        }
 
         public void append(char c)
         {
diff --git a/src/main/java/carpet/script/utils/ShapesRenderer.java b/src/main/java/carpet/script/utils/ShapesRenderer.java
index c2a799b9..78020763 100644
--- a/src/main/java/carpet/script/utils/ShapesRenderer.java
+++ b/src/main/java/carpet/script/utils/ShapesRenderer.java
@@ -39,16 +39,13 @@ public class ShapesRenderer
     {
         this.client = minecraftClient;
         shapes = new HashMap<>();
-        shapes.put(DimensionType.OVERWORLD, new Long2ObjectOpenHashMap<>());
-        shapes.put(DimensionType.THE_NETHER, new Long2ObjectOpenHashMap<>());
-        shapes.put(DimensionType.THE_END, new Long2ObjectOpenHashMap<>());
     }
 
     public void render(Camera camera, float partialTick)
     {
         IWorld iWorld = this.client.world;
         DimensionType dimensionType = iWorld.getDimension().getType();
-        if (shapes.get(dimensionType).isEmpty()) return;
+        if (shapes.get(dimensionType) == null || shapes.get(dimensionType).isEmpty()) return;
         long currentTime = client.world.getTime();
         RenderSystem.disableTexture();
         RenderSystem.enableDepthTest();
@@ -59,8 +56,7 @@ public class ShapesRenderer
         //RenderSystem.shadeModel(7425);
         RenderSystem.shadeModel(GL11.GL_FLAT);
         RenderSystem.enableAlphaTest();
-        RenderSystem.defaultAlphaFunc();
-        //RenderSystem.alphaFunc(GL11.GL_GREATER, 0.005F);
+        RenderSystem.alphaFunc(GL11.GL_GREATER, 0.003f);
         RenderSystem.disableCull();
         RenderSystem.disableLighting();
         RenderSystem.depthMask(false);
@@ -131,7 +127,7 @@ public class ShapesRenderer
             long key = rshape.key();
             synchronized (shapes)
             {
-                RenderedShape existing = shapes.get(dim).get(key);
+                RenderedShape existing = shapes.computeIfAbsent(dim, d -> new Long2ObjectOpenHashMap<>()).get(key);
                 if (existing != null)
                 {   // promoting previous shape
                     existing.expiryTick = rshape.expiryTick;
diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java
index 0f921b01..ca9e5ee1 100644
--- a/src/main/java/carpet/script/value/BlockValue.java
+++ b/src/main/java/carpet/script/value/BlockValue.java
@@ -12,7 +12,10 @@ import net.minecraft.block.Blocks;
 import net.minecraft.item.ItemPlacementContext;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.server.world.ServerWorld;
+import net.minecraft.state.property.Property;
 import net.minecraft.util.GlobalPos;
 import net.minecraft.util.Hand;
 import net.minecraft.util.hit.BlockHitResult;
@@ -24,8 +27,10 @@ import net.minecraft.world.World;
 import net.minecraft.world.chunk.WorldChunk;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
 
@@ -180,6 +185,33 @@ public class BlockValue extends Value
 
     public ServerWorld getWorld() { return world;}
 
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        // follows falling block convertion
+        CompoundTag tag =  new CompoundTag();
+        CompoundTag state = new CompoundTag();
+        BlockState s = getBlockState();
+        state.put("Name", StringTag.of(Registry.BLOCK.getId(s.getBlock()).toString()));
+        Collection> properties = s.getProperties();
+        if (!properties.isEmpty())
+        {
+            CompoundTag props = new CompoundTag();
+            for (Property p: properties)
+            {
+                props.put(p.getName(), StringTag.of(s.get(p).toString().toLowerCase(Locale.ROOT)));
+            }
+            state.put("Properties", props);
+        }
+        tag.put("BlockState", state);
+        CompoundTag dataTag = getData();
+        if (dataTag != null)
+        {
+            tag.put("TileEntityData", dataTag);
+        }
+        return tag;
+    }
 
     public enum SpecificDirection {
         UP("up",0.5, 0.0, 0.5, Direction.UP),
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 0b053f2c..dc45cf72 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -1,6 +1,5 @@
 package carpet.script.value;
 
-import carpet.CarpetSettings;
 import carpet.fakes.EntityInterface;
 import carpet.fakes.ItemEntityInterface;
 import carpet.fakes.LivingEntityInterface;
@@ -12,10 +11,10 @@ import carpet.script.CarpetContext;
 import carpet.script.EntityEventsGroup;
 import carpet.script.argument.Vector3Argument;
 import carpet.script.exception.InternalExpressionException;
-import carpet.utils.Messenger;
 import com.mojang.brigadier.StringReader;
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
-import net.minecraft.entity.player.HungerManager;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
 import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
 import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
@@ -61,6 +60,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.UUID;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Predicate;
@@ -87,11 +87,11 @@ public class EntityValue extends Value
             EntitySelector entitySelector = selectorCache.get(selector);
             if (entitySelector != null)
             {
-                return entitySelector.getEntities(source);
+                return entitySelector.getEntities(source.withMaxLevel(4));
             }
             entitySelector = new EntitySelectorReader(new StringReader(selector), true).read();
             selectorCache.put(selector, entitySelector);
-            return entitySelector.getEntities(source);
+            return entitySelector.getEntities(source.withMaxLevel(4));
         }
         catch (CommandSyntaxException e)
         {
@@ -289,6 +289,10 @@ public class EntityValue extends Value
         put("sneaking", (e, a) -> e.isSneaking()?Value.TRUE:Value.FALSE);
         put("sprinting", (e, a) -> e.isSprinting()?Value.TRUE:Value.FALSE);
         put("swimming", (e, a) -> e.isSwimming()?Value.TRUE:Value.FALSE);
+        put("persistence", (e, a) -> {
+            if (e instanceof MobEntity) return new NumericValue(((MobEntity) e).isPersistent());
+            return Value.NULL;
+        });
         put("hunger", (e, a) -> {
             if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getHungerManager().getFoodLevel());
             return Value.NULL;
@@ -520,6 +524,10 @@ public class EntityValue extends Value
         {
             throw new InternalExpressionException("'modify' for '"+what+"' expects a value");
         }
+        catch (IndexOutOfBoundsException ind)
+        {
+            throw new InternalExpressionException("Wrong number of arguments for `modify` option: "+what);
+        }
     }
 
     private static void updatePosition(Entity e, double x, double y, double z, float yaw, float pitch)
@@ -715,9 +723,23 @@ public class EntityValue extends Value
                 e.setCustomName(null);
                 return;
             }
-            e.setCustomNameVisible(true);
+            boolean showName = false;
+            if (v instanceof ListValue)
+            {
+                showName = ((ListValue) v).getItems().get(1).getBoolean();
+                v = ((ListValue) v).getItems().get(0);
+            }
+            e.setCustomNameVisible(showName);
             e.setCustomName(new LiteralText(v.getString()));
         });
+
+        put("persistence", (e, v) ->
+        {
+            if (!(e instanceof MobEntity)) return;
+            if (v == null) v = Value.TRUE;
+            ((MobEntityInterface)e).setPersistence(v.getBoolean());
+        });
+
         put("dismount", (e, v) -> e.stopRiding() );
         put("mount", (e, v) -> {
             if (v instanceof EntityValue)
@@ -976,22 +998,37 @@ public class EntityValue extends Value
             if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong());
         });
 
-        // gamemode         [check]
-        // spectate         [check]
-        // "fire"           [check]
-        // "extinguish"     [set fire ticks to 0]
-        // "silent"         [check]
-        // "gravity"        [check]
-        // "invulnerable"   [check]
+        put("nbt", (e, v) -> {
+            if (!(e instanceof PlayerEntity))
+            {
+                UUID uUID = e.getUuid();
+                Value tagValue = NBTSerializableValue.fromValue(v);
+                if (tagValue instanceof NBTSerializableValue)
+                {
+                    e.fromTag(((NBTSerializableValue) tagValue).getCompoundTag());
+                    e.setUuid(uUID);
+                }
+            }
+        });
+        put("nbt_merge", (e, v) -> {
+            if (!(e instanceof PlayerEntity))
+            {
+                UUID uUID = e.getUuid();
+                Value tagValue = NBTSerializableValue.fromValue(v);
+                if (tagValue instanceof NBTSerializableValue)
+                {
+                    CompoundTag nbttagcompound = e.toTag((new CompoundTag()));
+                    nbttagcompound.copyFrom(((NBTSerializableValue) tagValue).getCompoundTag());
+                    e.fromTag(nbttagcompound);
+                    e.setUuid(uUID);
+                }
+            }
+        });
+
         // "dimension"      []
         // "item"           []
         // "count",         []
-        // "age",           [check]
         // "effect_"name    []
-        // "hold"           [inventory_set?]
-        // "hold_offhand"   [inventory_set?] 
-        // "jump"           [check]
-        // "nbt" <-big one, for now use run('data merge entity ...
     }};
 
     public void setEvent(CarpetContext cc, String eventName, FunctionValue fun, List args)
@@ -1001,4 +1038,14 @@ public class EntityValue extends Value
             throw new InternalExpressionException("Unknown entity event: " + eventName);
         ((EntityInterface)entity).getEventContainer().addEvent(event, cc, fun, args);
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        CompoundTag tag = new CompoundTag();
+        tag.put("Data", getEntity().toTag( new CompoundTag()));
+        tag.put("Name", StringTag.of(Registry.ENTITY_TYPE.getId(entity.getType()).toString()));
+        return tag;
+    }
 }
diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java
index 501e3181..68983085 100644
--- a/src/main/java/carpet/script/value/FormattedTextValue.java
+++ b/src/main/java/carpet/script/value/FormattedTextValue.java
@@ -1,6 +1,9 @@
 package carpet.script.value;
 
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.text.BaseText;
+import net.minecraft.text.Text;
 
 public class FormattedTextValue extends StringValue
 {
@@ -37,4 +40,11 @@ public class FormattedTextValue extends StringValue
     {
         return text;
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        return StringTag.of(Text.Serializer.toJson(text));
+    }
 }
diff --git a/src/main/java/carpet/script/value/FrameworkValue.java b/src/main/java/carpet/script/value/FrameworkValue.java
index 882c79ec..a432974e 100644
--- a/src/main/java/carpet/script/value/FrameworkValue.java
+++ b/src/main/java/carpet/script/value/FrameworkValue.java
@@ -1,5 +1,7 @@
 package carpet.script.value;
 
+import net.minecraft.nbt.Tag;
+
 public abstract class FrameworkValue extends Value
 {
     @Override
@@ -25,4 +27,10 @@ public abstract class FrameworkValue extends Value
     {
         throw new UnsupportedOperationException("Scarpet language component cannot be used as map key");
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        throw new UnsupportedOperationException("Scarpet language component cannot be serialized to the tag");
+    }
 }
diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java
index 3b872ccf..45791fad 100644
--- a/src/main/java/carpet/script/value/FunctionValue.java
+++ b/src/main/java/carpet/script/value/FunctionValue.java
@@ -13,6 +13,8 @@ import carpet.script.exception.ExpressionException;
 import carpet.script.exception.InternalExpressionException;
 import carpet.script.exception.ReturnStatement;
 import carpet.script.exception.ThrowStatement;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -94,6 +96,8 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
         return name.hashCode()+(int)variant;
     }
 
+
+
     @Override
     public boolean equals(Object o)
     {
@@ -231,4 +235,11 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
     {
         return args;
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        return StringTag.of(getString());
+    }
 }
diff --git a/src/main/java/carpet/script/value/GlobalValue.java b/src/main/java/carpet/script/value/GlobalValue.java
index 697d630d..c38afb68 100644
--- a/src/main/java/carpet/script/value/GlobalValue.java
+++ b/src/main/java/carpet/script/value/GlobalValue.java
@@ -1,6 +1,7 @@
 package carpet.script.value;
 
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.Tag;
 
 public class GlobalValue extends Value
 {
@@ -28,4 +29,10 @@ public class GlobalValue extends Value
     {
         throw new UnsupportedOperationException("Global value cannot be used as a map key");
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        throw new UnsupportedOperationException("Global value cannot be serialized to the tag");
+    }
 }
diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java
index e57d7110..2edd637d 100644
--- a/src/main/java/carpet/script/value/LazyListValue.java
+++ b/src/main/java/carpet/script/value/LazyListValue.java
@@ -1,6 +1,8 @@
 package carpet.script.value;
 
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -152,4 +154,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
     {
         return ("i"+getString()).hashCode();
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        return StringTag.of(getString());
+    }
 }
diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java
index e20d32b3..4aca8e41 100644
--- a/src/main/java/carpet/script/value/ListValue.java
+++ b/src/main/java/carpet/script/value/ListValue.java
@@ -3,13 +3,23 @@ package carpet.script.value;
 import carpet.script.LazyValue;
 import carpet.script.exception.InternalExpressionException;
 import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.AbstractNumberTag;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.DoubleTag;
+import net.minecraft.nbt.IntTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.LongTag;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
 import net.minecraft.util.registry.Registry;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
@@ -98,6 +108,13 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
     {
         return ListValue.wrap(Arrays.asList(list));
     }
+    public static ListValue ofNums(Number ... list)
+    {
+        List valList = new ArrayList<>();
+        for (Number i : list)
+            valList.add(new NumericValue(i.doubleValue()));
+        return ListValue.wrap(valList);
+    }
 
     public static LazyValue lazyEmpty()
     {
@@ -447,4 +464,65 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
     {
         return items.hashCode();
     }
+
+    private enum TagTypeCompat
+    {
+        INT,
+        LONG,
+        DBL,
+        LIST,
+        MAP,
+        STRING;
+        private static TagTypeCompat getType(Tag tag)
+        {
+            if (tag instanceof IntTag) return INT;
+            if (tag instanceof LongTag) return LONG;
+            if (tag instanceof DoubleTag) return DBL;
+            if (tag instanceof ListTag) return LIST;
+            if (tag instanceof CompoundTag) return MAP;
+            return STRING;
+        }
+    }
+
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        int argSize = items.size();
+        if (argSize == 0) return new ListTag();
+        ListTag tag = new ListTag();
+        if (argSize ==1)
+        {
+            tag.add(items.get(0).toTag(force));
+            return tag;
+        }
+        // figuring out the types
+        List tags= new ArrayList<>();
+        items.forEach(v -> tags.add(v.toTag(force)));
+        Set cases = EnumSet.noneOf(TagTypeCompat.class);
+        tags.forEach(t -> cases.add(TagTypeCompat.getType(t)));
+        if (cases.size()==1) // well, one type of items
+        {
+            tag.addAll(tags);
+            return tag;
+        }
+        if (cases.contains(TagTypeCompat.LIST)
+                || cases.contains(TagTypeCompat.MAP)
+                || cases.contains(TagTypeCompat.STRING)) // incompatible types
+        {
+            if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+            tags.forEach(t -> tag.add(StringTag.of(t.asString())));
+            return tag;
+        }
+        // only numbers / mixed types
+        if (cases.contains(TagTypeCompat.DBL))
+        {
+            tags.forEach(t -> tag.add(DoubleTag.of(((AbstractNumberTag)t).getDouble())));
+        }
+        else
+        {
+            tags.forEach(t -> tag.add(LongTag.of(((AbstractNumberTag)t).getLong())));
+        }
+        return tag;
+    }
 }
diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java
index 61965e4e..f6332b4d 100644
--- a/src/main/java/carpet/script/value/MapValue.java
+++ b/src/main/java/carpet/script/value/MapValue.java
@@ -1,6 +1,8 @@
 package carpet.script.value;
 
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.Tag;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -226,4 +228,16 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa
     {
         return map.hashCode();
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        CompoundTag tag = new CompoundTag() ;
+        map.forEach( (k, v) ->
+        {
+            if (!force && !(k instanceof StringValue)) throw new NBTSerializableValue.IncompatibleTypeException(k);
+            tag.put(k.getString(), v.toTag(force));
+        });
+        return tag;
+    }
 }
diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java
index b4ce70b5..c2ff37e0 100644
--- a/src/main/java/carpet/script/value/NBTSerializableValue.java
+++ b/src/main/java/carpet/script/value/NBTSerializableValue.java
@@ -33,6 +33,7 @@ import net.minecraft.util.Identifier;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Box;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -48,6 +49,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     private String nbtString = null;
     private Tag nbtTag = null;
     private Supplier nbtSupplier = null;
+    private boolean owned = false;
 
     private NBTSerializableValue() {}
 
@@ -64,11 +66,18 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
                 throw new InternalExpressionException("Incorrect NBT data: "+nbtString);
             }
         };
+        owned = true;
     }
 
     public NBTSerializableValue(Tag tag)
     {
         nbtTag = tag;
+        owned = true;
+    }
+
+    public NBTSerializableValue(Supplier tagSupplier)
+    {
+        nbtSupplier = tagSupplier;
     }
 
     public static Value fromStack(ItemStack stack)
@@ -100,7 +109,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
         }
         catch (CommandSyntaxException e)
         {
-            return null;
+           throw new InternalExpressionException("Incorrect NBT tag: nbtString");
         }
         NBTSerializableValue value = new NBTSerializableValue(tag);
         value.nbtString = nbtString;
@@ -115,6 +124,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
         NBTSerializableValue copy = new NBTSerializableValue(nbtTag);
         copy.nbtSupplier = this.nbtSupplier;
         copy.nbtString = this.nbtString;
+        copy.owned = this.owned;
         return copy;
     }
 
@@ -122,9 +132,8 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     public Value deepcopy()
     {
         NBTSerializableValue copy = (NBTSerializableValue) clone();
-        // same fields except when tag is set - need to copy it.
-        if (copy.nbtTag != null)
-            copy.nbtTag = copy.getTag().copy();
+        copy.owned = false;
+        ensureOwnership();
         return copy;
     }
 
@@ -290,7 +299,50 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     private static Value decodeTag(Tag t)
     {
         if (t instanceof CompoundTag)
-            return new NBTSerializableValue(t);
+            return new NBTSerializableValue(() -> t);
+        if (t instanceof AbstractNumberTag)
+            return new NumericValue(((AbstractNumberTag) t).getDouble());
+        // more can be done here
+        return new StringValue(t.asString());
+    }
+
+    public Value toValue()
+    {
+        return decodeTagDeep(this.getTag());
+    }
+
+    public static Value fromValue(Value v)
+    {
+        if (v instanceof NBTSerializableValue)
+            return v;
+        if (v instanceof NullValue)
+            return Value.NULL;
+        return NBTSerializableValue.parseString(v.getString());
+    }
+
+
+    private static Value decodeTagDeep(Tag t)
+    {
+        if (t instanceof CompoundTag)
+        {
+            Map pairs = new HashMap<>();
+            CompoundTag ctag = (CompoundTag)t;
+            for (String key: ctag.getKeys())
+            {
+                pairs.put(new StringValue(key), decodeTagDeep(ctag.get(key)));
+            }
+            return MapValue.wrap(pairs);
+        }
+        if (t instanceof AbstractListTag)
+        {
+            List elems = new ArrayList<>();
+            AbstractListTag ltag = (AbstractListTag)t;
+            for (Tag elem: ltag)
+            {
+                elems.add(decodeTagDeep(elem));
+            }
+            return ListValue.wrap(elems);
+        }
         if (t instanceof AbstractNumberTag)
             return new NumericValue(((AbstractNumberTag) t).getDouble());
         // more can be done here
@@ -304,13 +356,6 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
         return nbtTag;
     }
 
-    private void replaceTag(Tag newTag)
-    {
-        nbtTag = newTag;
-        nbtString = null;
-        nbtSupplier = null;  // just to be sure
-    }
-
     @Override
     public boolean equals(final Object o)
     {
@@ -346,6 +391,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     {
         try
         {
+            ensureOwnership();
             return (CompoundTag) getTag();
         }
         catch (ClassCastException e)
@@ -364,12 +410,12 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     public boolean put(Value where, Value value, Value conditions)
     {
         /// WIP
-
+        ensureOwnership();
         NbtPathArgumentType.NbtPath path = cachePath(where.getString());
         Tag tagToInsert = value instanceof NBTSerializableValue ?
                 ((NBTSerializableValue) value).getTag() :
                 new NBTSerializableValue(value.getString()).getTag();
-        Tag modifiedTag;
+        boolean modifiedTag;
         if (conditions instanceof NumericValue)
         {
             modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert);
@@ -390,22 +436,17 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
                 return false;
             }
         }
-        if (modifiedTag != null)
-        {
-            replaceTag(modifiedTag);
-            return  true;
-        }
-        return false;
+        return modifiedTag;
     }
 
 
 
-    private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement)
+    private boolean modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement)
     {
-        return modify_insert(index, nbtPath, newElement, this.getTag().copy());
+        return modify_insert(index, nbtPath, newElement, this.getTag());
     }
 
-    private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag)
+    private boolean modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag)
     {
         Collection targets;
         try
@@ -414,7 +455,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
         }
         catch (CommandSyntaxException e)
         {
-            return null;
+            return false;
         }
 
         boolean modified = false;
@@ -428,27 +469,27 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
             {
                 AbstractListTag targetList = (AbstractListTag) target;
                 if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy()))
-                    return null;
+                    return false;
                 modified = true;
             }
             catch (IndexOutOfBoundsException ignored)
             {
             }
         }
-        return modified?currentTag:null;
+        return modified;
     }
 
 
-    private Tag modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
+    private boolean modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
     {
         if (!(replacement instanceof CompoundTag))
         {
-            return getTag();
+            return false;
         }
-        Tag originalTag = getTag().copy();
+        Tag ownTag = getTag();
         try
         {
-            for (Tag target : nbtPath.getOrInit(originalTag, CompoundTag::new))
+            for (Tag target : nbtPath.getOrInit(ownTag, CompoundTag::new))
             {
                 if (!(target instanceof CompoundTag))
                 {
@@ -457,23 +498,26 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
                 ((CompoundTag) target).copyFrom((CompoundTag) replacement);
             }
         }
-        catch (CommandSyntaxException ignored) { }
-        return originalTag;
+        catch (CommandSyntaxException ignored)
+        {
+            return false;
+        }
+        return true;
     }
 
-    private Tag modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
+    private boolean modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
     {
-        Tag originalTag = getTag().copy();
+        Tag tag = getTag();
         String pathText = nbtPath.toString();
         if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement
         {
-            if (nbtPath.remove(originalTag)==0)
-                return null;
+            if (nbtPath.remove(tag)==0)
+                return false;
             Pattern pattern = Pattern.compile("\\[[^\\[]*]$");
             Matcher matcher = pattern.matcher(pathText);
             if (!matcher.find()) // malformed path
             {
-                return null;
+                return false;
             }
             String arrAccess = matcher.group();
             int pos;
@@ -487,21 +531,21 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
                 }
                 catch (NumberFormatException e)
                 {
-                    return null;
+                    return false;
                 }
             }
             NbtPathArgumentType.NbtPath newPath = cachePath(pathText.substring(0, pathText.length()-arrAccess.length()));
-            return modify_insert(pos,newPath,replacement, originalTag);
+            return modify_insert(pos,newPath,replacement, tag);
         }
         try
         {
-            nbtPath.put(originalTag, () -> replacement);
+            nbtPath.put(tag, () -> replacement);
         }
         catch (CommandSyntaxException e)
         {
-            return null;
+            return false;
         }
-        return originalTag;
+        return true;
     }
 
     @Override
@@ -527,13 +571,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
         return cachePath(where.getString()).count(getTag()) > 0;
     }
 
+    private void ensureOwnership()
+    {
+        if (!owned)
+        {
+            nbtTag = getTag().copy();
+            nbtString = null;
+            nbtSupplier = null;  // just to be sure
+            owned = true;
+        }
+    }
+
     @Override
     public boolean delete(Value where)
     {
         NbtPathArgumentType.NbtPath path = cachePath(where.getString());
-        Tag tag = getTag().copy();
-        int removed = path.remove(tag);
-        if (removed > 0) replaceTag(tag);
+        ensureOwnership();
+        int removed = path.remove(getTag());
         return removed > 0;
     }
 
@@ -584,4 +638,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
     {
         return "nbt";
     }
+
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        ensureOwnership();
+        return getTag();
+    }
+
+    public static class IncompatibleTypeException extends RuntimeException
+    {
+        private IncompatibleTypeException() {}
+        public Value val;
+        public IncompatibleTypeException(Value val)
+        {
+            this.val = val;
+        }
+    };
 }
diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java
index eeef2f0d..18ffcec1 100644
--- a/src/main/java/carpet/script/value/NullValue.java
+++ b/src/main/java/carpet/script/value/NullValue.java
@@ -1,5 +1,8 @@
 package carpet.script.value;
 
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
+
 public class NullValue extends NumericValue // TODO check nonsingleton code
 {
     @Override
@@ -50,4 +53,11 @@ public class NullValue extends NumericValue // TODO check nonsingleton code
     {
         return 0;
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        return StringTag.of("null");
+    }
 }
diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java
index 2ae575fb..482d9fab 100644
--- a/src/main/java/carpet/script/value/NumericValue.java
+++ b/src/main/java/carpet/script/value/NumericValue.java
@@ -1,6 +1,10 @@
 package carpet.script.value;
 
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.DoubleTag;
+import net.minecraft.nbt.IntTag;
+import net.minecraft.nbt.LongTag;
+import net.minecraft.nbt.Tag;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -204,4 +208,20 @@ public class NumericValue extends Value
     {
         return (int)getLong();
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        long longValue = getLong();
+        if (value == (double)longValue)
+        {
+            if (abs(value) < Integer.MAX_VALUE-2)
+                return IntTag.of((int)longValue);
+            return LongTag.of(getLong());
+        }
+        else
+        {
+            return DoubleTag.of(value);
+        }
+    }
 }
diff --git a/src/main/java/carpet/script/value/StringValue.java b/src/main/java/carpet/script/value/StringValue.java
index eba0cd16..c494865d 100644
--- a/src/main/java/carpet/script/value/StringValue.java
+++ b/src/main/java/carpet/script/value/StringValue.java
@@ -1,5 +1,8 @@
 package carpet.script.value;
 
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
+
 public class StringValue extends Value
 {
     private String str;
@@ -30,4 +33,10 @@ public class StringValue extends Value
     {
         return "string";
     }
+
+    @Override
+    public Tag toTag(boolean force)
+    {
+        return StringTag.of(str);
+    }
 }
diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java
index 98ac7623..e4ee5d50 100644
--- a/src/main/java/carpet/script/value/ThreadValue.java
+++ b/src/main/java/carpet/script/value/ThreadValue.java
@@ -6,6 +6,7 @@ import carpet.script.LazyValue;
 import carpet.script.Tokenizer;
 import carpet.script.exception.ExpressionException;
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.Tag;
 
 import java.util.HashMap;
 import java.util.List;
@@ -144,4 +145,10 @@ public class ThreadValue extends Value
         locks.clear();
     }
 
+    @Override
+    public Tag toTag(boolean force)
+    {
+        if (!force) throw new NBTSerializableValue.IncompatibleTypeException(this);
+        return getValue().toTag(true);
+    }
 }
diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java
index f97fb8d5..82bdd664 100644
--- a/src/main/java/carpet/script/value/Value.java
+++ b/src/main/java/carpet/script/value/Value.java
@@ -1,6 +1,7 @@
 package carpet.script.value;
 
 import carpet.script.exception.InternalExpressionException;
+import net.minecraft.nbt.Tag;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -203,4 +204,6 @@ public abstract class Value implements Comparable, Cloneable
             throw new InternalExpressionException("Cannot make a copy of value: "+this);
         }
     }
+
+    public abstract Tag toTag(boolean force);
 }
diff --git a/src/main/java/carpet/settings/SettingsManager.java b/src/main/java/carpet/settings/SettingsManager.java
index b792de08..0c8803d3 100644
--- a/src/main/java/carpet/settings/SettingsManager.java
+++ b/src/main/java/carpet/settings/SettingsManager.java
@@ -470,7 +470,7 @@ public class SettingsManager
         writeSettingsToConf(conf.getLeft()); // this may feels weird, but if conf
         // is locked, it will never reach this point.
         rule.set(source,stringValue);
-        Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_will_now_default_to_%(value)s","rule %s will now default to %s"), rule.translatedName(), stringValue));
+        Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_will_now_default_to_%(value)s","Rule %s will now default to %s"), rule.translatedName(), stringValue));
         return 1;
     }
     // removes overrides of the default values in the file
@@ -482,6 +482,7 @@ public class SettingsManager
         conf.getLeft().remove(rule.name);
         writeSettingsToConf(conf.getLeft());
         rules.get(rule.name).resetToDefault(source);
+        Messenger.m(source ,"gi "+String.format(tr("ui.rule_%(rule)s_not_set_restart","Rule %s will now not be set on restart"), rule.translatedName()));
         return 1;
     }
 
diff --git a/src/main/resources/assets/carpet/lang/en_us.json b/src/main/resources/assets/carpet/lang/en_us.json
index 34331d5c..100547f6 100644
--- a/src/main/resources/assets/carpet/lang/en_us.json
+++ b/src/main/resources/assets/carpet/lang/en_us.json
@@ -47,7 +47,8 @@
   "ui.tags": "Tags",
   "ui.change_permanently": "Change permanently",
   "ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "click to keep the settings in %s to save across restarts",
-  "ui.rule_%(rule)s_will_now_default_to_%(value)s": "rule %s will now default to %s",
+  "ui.rule_%(rule)s_will_now_default_to_%(value)s": "Rule %s will now default to %s",
+  "ui.rule_%(rule)s_not_set_restart": "Rule %s will now not be set on restart",
   "ui.current_value": "current value"
 
 }
diff --git a/src/main/resources/assets/carpet/lang/zh_cn.json b/src/main/resources/assets/carpet/lang/zh_cn.json
index ef0c0124..248faac4 100644
--- a/src/main/resources/assets/carpet/lang/zh_cn.json
+++ b/src/main/resources/assets/carpet/lang/zh_cn.json
@@ -1,8 +1,8 @@
 {
-  "rule.antiCheatDisabled.name": "移动检测通杀",
+  "rule.antiCheatDisabled.name": "禁用反作弊移动监测",
   "rule.antiCheatDisabled.desc": "防止玩家因为“本服务器未启用飞行”,“移动过快”等原因被踢出",
   
-  "rule.carpets.name": "放置地毯执行功能",
+  "rule.carpets.name": "多功能地毯",
   "rule.carpets.desc": "通过放置地毯方块快捷执行部分功能",
   
   "rule.combineXPOrbs.name": "经验球合并",
@@ -135,7 +135,7 @@
   "rule.pushLimit.desc": "自定义活塞推动上限",
   "rule.pushLimit.extra.0": "你必须在1-1024中选取一个值",
   
-  "rule.quasiConnectivity.name": "qC激活开关",
+  "rule.quasiConnectivity.name": "半连接激活开关",
   "rule.quasiConnectivity.desc": "活塞,发射器和投掷器在他们上方方块激活时是否响应",
   "rule.quasiConnectivity.extra.0": "设置为true为开启 设置为false为禁用",
   
@@ -228,7 +228,8 @@
   "ui.tags": "分类",
   "ui.change_permanently": "永久更改",
   "ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "点击此按钮以使此选项保存于%s,重启后依旧生效",
-  "ui.rule_%(rule)s_will_now_default_to_%(value)s": "规则%s将会被默认设置为%s",
+  "ui.rule_%(rule)s_will_now_default_to_%(value)s": "规则“%s”将会被默认设置为%s",
+  "ui.rule_%(rule)s_not_set_restart": "规则“%s”将在重启后恢复默认值",
   "ui.current_value": "当前的值"
 
 }
\ No newline at end of file
diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc
index 993825db..27cb4244 100644
--- a/src/main/resources/assets/carpet/scripts/camera.sc
+++ b/src/main/resources/assets/carpet/scripts/camera.sc
@@ -331,7 +331,7 @@ select(num) ->
     );
     global_selected_point = if (global_selected_point == selected_point, null, selected_point);
     global_needs_updating = true;
-    // no need to _update since path is still valid
+
 );
 
 // player can also punch the mannequin to select/deselect it
@@ -501,7 +501,7 @@ play() ->
    task( _() -> (
        if (global_playing_path, // we don't want to join_task not to lock it just in case. No need to panic here
            global_playing_path = false; // cancel current path rendering
-           sleep(1500); // in case we interrupt previous playbacks
+           sleep(1500);
        );
        showing_path = global_showing_path;
        hide();
diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc
index 1659d565..c6934911 100644
--- a/src/main/resources/assets/carpet/scripts/event_test.sc
+++ b/src/main/resources/assets/carpet/scripts/event_test.sc
@@ -244,3 +244,23 @@ __on_player_disconnects(player, reason) ->
     print('__on_player_disconnects(player)');
     print('Player '+player+' disconnects because: '+reason)
 );
+
+__on_player_chooses_recipe(player, recipe, full_stack) ->
+(
+    print('');
+    print('__on_player_chooses_recipe(player, recipe, full_stack)');
+    print('player chooses recipe:');
+    print('  - player: '+player);
+    print('  - recipe: '+recipe);
+    print('  - full stack: '+ full_stack);
+);
+
+__on_player_switches_slot(player, from, to) ->
+(
+    print('');
+    print('__on_player_switches_slot(player, from, to)');
+    print('player switches inventory slot:');
+    print('  - player: '+player);
+    print('  - from: '+ from);
+    print('  - to: '+ to);
+);
diff --git a/src/main/resources/assets/carpet/scripts/overlay.sc b/src/main/resources/assets/carpet/scripts/overlay.sc
index 8038b9d3..ea0a140b 100644
--- a/src/main/resources/assets/carpet/scripts/overlay.sc
+++ b/src/main/resources/assets/carpet/scripts/overlay.sc
@@ -94,7 +94,7 @@ __structure_renderer(player_name) ->
 		    l(from, to) = structure_data;
 		    total_size = _euclidean(from, to);
 		    density = max(10, total_size/10);
-		    draw_shape('box', 15, 'from', from, 'to', to+1, 'color', 0x00FFFFFF, 'line', 3, 'fill', 0x00FFFF22);
+		    draw_shape('box', 15, 'from', from, 'to', to+1, 'color', 0x00FFFFFF, 'line', 3, 'fill', 0x00FFFF12);
 		    structure_pieces = slice(sort_key(structure_pieces, _euclidean_sq((_:2+_:3)/2,ppos)), 0, config:'max_pieces');
 		    for (structure_pieces, l(piece, direction, from, to) = _;
 		        r = 255 - floor(128 * _i/config:'max_pieces');
diff --git a/src/main/resources/carpet.accesswidener b/src/main/resources/carpet.accesswidener
index 901a39b9..e9dad565 100644
--- a/src/main/resources/carpet.accesswidener
+++ b/src/main/resources/carpet.accesswidener
@@ -2,3 +2,4 @@ accessWidener	v1	named
 
 accessible class net/minecraft/server/world/ThreadedAnvilChunkStorage$TicketManager
 accessible class net/minecraft/server/world/ServerLightingProvider$Stage
+accessible class net/minecraft/recipe/Ingredient$Entry
\ No newline at end of file
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index a82720f7..1c967f48 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -169,6 +169,9 @@
     "ServerWorld_scarpetEventMixin",
     "HungerManagerMixin_scarpetEventsMixin",
 
+    "RecipeManager_scarpetMixin",
+    "Ingredient_scarpetMixin",
+
     "PistonBlock_movableTEMixin",
     "PistonBlockEntity_movableTEMixin",
     "World_movableTEMixin",
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 07954291..9ebbd519 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,7 +1,7 @@
 {
   "schemaVersion": 1,
   "id": "carpet",
-  "version": "1.3.29",
+  "version": "1.4.0",
 
   "name": "Carpet Mod in Fabric",
   "description": "Carpet made out of fabric",
@@ -13,7 +13,7 @@
     "sources": "https://github.com/gnembon/fabric-carpet"
   },
 
-  "license": "GNU LGPL V3",
+  "license": "MIT",
   "icon": "assets/carpet/icon.png",
 
   "environment": "*",
@@ -24,7 +24,8 @@
   "mixins": [
     "carpet.mixins.json"
   ],
-  "accesswidener": "carpet.accesswidener",
+  "accessWidener" : "carpet.accesswidener",
+
   "depends": {
     "minecraft": "1.15.x",
     "fabricloader": ">=0.7.1"