Merge branch 'master' into light

# Conflicts:
#	src/main/resources/carpet.accesswidener
#	src/main/resources/fabric.mod.json
This commit is contained in:
gnembon committed 2020-07-01 00:11:35 -03:00
commit 7067e59a21
66 files changed
+1898 -495

No files matched your search

+17 -161
View File
@@ -1,165 +1,21 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
MIT License
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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.
+1 -1
View File
@@ -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)
+228 -48
View File
@@ -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)
</pre>
## 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
<pre>
@@ -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.
<pre>
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))
</pre>
### `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:
<pre>
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
)
)
</pre>
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]
</pre>
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:
<pre>
{ 'foo' -> 'bar' } => {l('foo', 'bar')}
</pre>
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.
<pre>
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]
</pre>
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]
</pre>
### `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()`.
<pre>
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}
</pre>
@@ -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
</pre>
### `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
</pre>
### `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
</pre>
### `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:
<pre>
recipe_data('iron_ingot_from_nuggets')
recipe_data('iron_ingot')
recipe_data('glass', 'smelting')
</pre>
### `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.
+27 -3
View File
@@ -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.
+14 -8
View File
@@ -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
</pre>
### `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.
+10 -3
View File
@@ -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
</pre>
### `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
+21 -2
View File
@@ -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.
<pre>
run('/kill ' + e~'command_name');
</pre>
@@ -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.
+2
View File
@@ -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)
+45
View File
@@ -38,6 +38,51 @@ item_category('ender_pearl') => misc
item_category('stone') => building_blocks
</pre>
### `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:
<pre>
recipe_data('iron_ingot_from_nuggets')
recipe_data('iron_ingot')
recipe_data('glass', 'smelting')
</pre>
### `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
+36 -23
View File
@@ -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.
<pre>
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]
</pre>
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]
</pre>
### `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()`.
<pre>
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}
</pre>
@@ -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]
</pre>
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:
<pre>
{ 'foo' -> 'bar' } => {l('foo', 'bar')}
</pre>
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)`
+20 -7
View File
@@ -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)
</pre>
## 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)
+51 -2
View File
@@ -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.
<pre>
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))
</pre>
### `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:
<pre>
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
)
)
</pre>
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.
@@ -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
<pre>
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,152 @@
<NotepadPlus>
<UserLang name="scarpet" ext="sc scl" udlVersion="2.1">
<Settings>
<Global caseIgnored="yes" allowFoldOfComments="no" foldCompact="no" forcePureLC="0" decimalSeparator="0" />
<Prefix Keywords1="no" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="yes" Keywords7="no" Keywords8="no" />
</Settings>
<KeywordLists>
<Keywords name="Comments">00// 01 02 03 04</Keywords>
<Keywords name="Numbers, prefix1">0x</Keywords>
<Keywords name="Numbers, prefix2"></Keywords>
<Keywords name="Numbers, extras1"></Keywords>
<Keywords name="Numbers, extras2"></Keywords>
<Keywords name="Numbers, suffix1"></Keywords>
<Keywords name="Numbers, suffix2"></Keywords>
<Keywords name="Numbers, range"></Keywords>
<Keywords name="Operators1">; + - * / % ^ &amp;&amp; || &gt; &gt;= &lt; &lt;= == != = += -&gt; &lt;&gt; ! ~ , :</Keywords>
<Keywords name="Operators2"></Keywords>
<Keywords name="Folders in code1, open">(</Keywords>
<Keywords name="Folders in code1, middle"></Keywords>
<Keywords name="Folders in code1, close">)</Keywords>
<Keywords name="Folders in code2, open"></Keywords>
<Keywords name="Folders in code2, middle"></Keywords>
<Keywords name="Folders in code2, close"></Keywords>
<Keywords name="Folders in comment, open"></Keywords>
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">
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
</Keywords>
<Keywords name="Keywords2">
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
</Keywords>
<Keywords name="Keywords3">euler pi null true false</Keywords>
<Keywords name="Keywords4">x y z p</Keywords>
<Keywords name="Keywords5">_ _i _a _x _y _z</Keywords>
<Keywords name="Keywords6">global_</Keywords>
<Keywords name="Keywords7">
__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
</Keywords>
<Keywords name="Keywords8">$</Keywords>
<Keywords name="Delimiters">00&apos; 01\ 02&apos; 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
</KeywordLists>
<Styles>
<WordsStyle name="DEFAULT" fgColor="000040" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="COMMENTS" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="LINE COMMENTS" fgColor="808080" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="NUMBERS" fgColor="008040" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS1" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS2" fgColor="800000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS3" fgColor="0080C0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS4" fgColor="400080" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS5" fgColor="FF0080" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
<WordsStyle name="KEYWORDS6" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS7" fgColor="FF8040" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="KEYWORDS8" fgColor="DEDEDE" bgColor="FFFFFF" fontName="" fontStyle="1" nesting="0" />
<WordsStyle name="OPERATORS" fgColor="804040" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN CODE1" fgColor="515151" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS1" fgColor="0080C0" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS2" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS3" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS4" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS5" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS7" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
<WordsStyle name="DELIMITERS8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" nesting="0" />
</Styles>
</UserLang>
</NotepadPlus>
+2 -2
View File
@@ -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
+10 -1
View File
@@ -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<String, String> canHasTranslations(String lang) { return null;}
}
+6 -1
View File
@@ -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);
+1 -1
View File
@@ -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;
@@ -0,0 +1,11 @@
package carpet.fakes;
import net.minecraft.item.ItemStack;
import java.util.Collection;
import java.util.List;
public interface IngredientInterface
{
List<Collection<ItemStack>> getRecipeStacks();
}
@@ -10,4 +10,6 @@ public interface MobEntityInterface
GoalSelector getAI(boolean target);
Map<String, Goal> getTemporaryTasks();
void setPersistence(boolean what);
}
@@ -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<Recipe<?>> getAllMatching(RecipeType type, Identifier output);
}
@@ -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;
@@ -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<Collection<ItemStack>> getRecipeStacks()
{
List<Collection<ItemStack>> res = Arrays.stream(entries).map(Ingredient.Entry::getStacks).collect(Collectors.toList());
return res;
}
}
@@ -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)
{
@@ -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(
@@ -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<ServerWorld> 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",
@@ -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<String, Goal> temporaryTasks = new HashMap<>();
@Override
@@ -30,5 +31,9 @@ public abstract class MobEntityMixin implements MobEntityInterface
return temporaryTasks;
}
@Override
public void setPersistence(boolean what)
{
persistent = what;
}
}
@@ -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<RecipeType<?>, Map<Identifier, Recipe<?>>> recipes;
@Override
public List<Recipe<?>> getAllMatching(RecipeType type, Identifier output)
{
Map<Identifier, Recipe<?>> 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()));
}
}
@@ -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());
}
}
}
@@ -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());
}
}
}
@@ -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)
));
}
}
@@ -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<ServerPlayerEntity, String> remoteCarpetPlayers = new HashMap<>();
private static Set<ServerPlayerEntity> validCarpetPlayers = new HashSet<>();
private static Map<String, BiConsumer<ServerPlayerEntity, Tag>> dataHandlers = new HashMap<String, BiConsumer<ServerPlayerEntity, Tag>>(){{
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<Text> 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)
{
@@ -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) { }
+344 -111
View File
@@ -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<Block, BlockState> states = sourceBlockState.getBlock().getStateManager();
for (int i = sourceLocator.offset; i < lv.size(); i += 2)
List<Value> 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<Block, BlockState> 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<Recipe<?>> 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<Value> recipesOutput = new ArrayList<>();
for (Recipe<?> recipe: recipes)
{
ItemStack result = recipe.getOutput();
List<Value> 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<Collection<ItemStack>> stacks = ((IngredientInterface) (Object) ingredient).getRecipeStacks();
if (stacks.isEmpty())
{
ingredientValue.add(Value.NULL);
}
else
{
List<Value> 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;
});
+2 -1
View File
@@ -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<String, LazyValue> variables = new HashMap<>();
public Map<String, LazyValue> variables = new HashMap<>();
public final ScriptHost host;
+78 -18
View File
@@ -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<Value> 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<Value> 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<Tokenizer.Token> cleanedTokens = tokenizer.fixSemicolons();
List<Tokenizer.Token> cleanedTokens = tokenizer.postProcess();
Tokenizer.Token lastFunction = null;
Tokenizer.Token previousToken = null;
+50 -6
View File
@@ -43,7 +43,7 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
this.newLinesMarkers = allowNewLineMakers;
}
public List<Token> fixSemicolons()
public List<Token> postProcess()
{
Iterable<Token> iterable = () -> this;
List<Token> originalTokens = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
@@ -52,6 +52,9 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
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<Tokenizer.Token>
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<Tokenizer.Token>
}
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<Tokenizer.Token>
{
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<Tokenizer.Token>
}
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<Tokenizer.Token>
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)
{
@@ -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;
@@ -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<Property<?>> 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),
@@ -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<Value> 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;
}
}
@@ -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));
}
}
@@ -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");
}
}
@@ -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());
}
}
@@ -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");
}
}
@@ -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());
}
}
@@ -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<Value> 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<Tag> tags= new ArrayList<>();
items.forEach(v -> tags.add(v.toTag(force)));
Set<TagTypeCompat> 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;
}
}
@@ -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;
}
}
@@ -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<Tag> 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<Tag> 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<Value, Value> 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<Value> elems = new ArrayList<>();
AbstractListTag<? extends Tag> ltag = (AbstractListTag<? extends Tag>)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<Tag> 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;
}
};
}
@@ -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");
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<Value>, Cloneable
throw new InternalExpressionException("Cannot make a copy of value: "+this);
}
}
public abstract Tag toTag(boolean force);
}
@@ -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;
}
@@ -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"
}
@@ -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": "当前的值"
}
@@ -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();
@@ -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);
);
@@ -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');
+1
View File
@@ -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
+3
View File
@@ -169,6 +169,9 @@
"ServerWorld_scarpetEventMixin",
"HungerManagerMixin_scarpetEventsMixin",
"RecipeManager_scarpetMixin",
"Ingredient_scarpetMixin",
"PistonBlock_movableTEMixin",
"PistonBlockEntity_movableTEMixin",
"World_movableTEMixin",
+4 -3
View File
@@ -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"