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 MIT License
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (c) 2020 gnembon
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
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 above copyright notice and this permission notice shall be included in all
the terms and conditions of version 3 of the GNU General Public copies or substantial portions of the Software.
License, supplemented by the additional permissions listed below.
0. Additional Definitions. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
As used herein, "this License" refers to version 3 of the GNU Lesser FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
General Public License, and the "GNU GPL" refers to version 3 of the GNU AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
General Public License. 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
"The Library" refers to a covered work governed by this License, SOFTWARE.
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.
+1 -1
View File
@@ -32,7 +32,7 @@
[Events](/docs/scarpet/api/Events.md) [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) [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 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 designed to provide the ability to write custom programs to run within Minecraft
and interact with the world. 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 This specification is divided into two sections: this one is agnostic to any
Minecraft related features and could function on its own, and CarpetExpression Minecraft related features and could function on its own, and CarpetExpression
for Minecraft specific routines and world manipulation functions. 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) check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
</pre> </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 # Variables and Constants
`scarpet` provides a number of constants that can be used literally in scripts `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 `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 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` 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 Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions
<pre> <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 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. 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, 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> <pre>
print('foo') => results in foo, prints: foo 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)) print(str('this took %d milliseconds',time()-start_time))
</pre> </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)` ### `profile_expr(expression)`
Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. 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 ->`
`->` 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 To organize code better than a flat sequence of operations, one can define functions. Definition is correct
if has the following form 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> </pre>
In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, 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)` ### `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 ## 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 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 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 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 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> <pre>
l(1,2,'foo') => [1, 2, foo] l(1,2,'foo') <=> [1, 2, 'foo']
l() => [] (empty list) l() <=> [] (empty list)
l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l(1, 2) = l(3, 4) => Error: l is not a variable [1, 2] = [3, 4] => Error: l is not a variable
l(foo, bar) = l(3,4); foo==3 && bar==4 => 1 [foo, bar] = [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] [foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz] => [3, 4, 5]
</pre> </pre>
In the last example `l(min(foo, bar), baz)` creates a valid L-value, as min(foo, bar) finds the lower of the 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 variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values
will be assigned new values will be assigned new values
### `join(delim, list), join(delim, 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] range(20, 10, -2) => [20, 18, 16, 14, 12]
</pre> </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 ## Map operations
Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. 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: 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 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 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 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> <pre>
m(1,2,'foo') => {1: null, 2: null, foo: null} {1, 2, 'foo'} => {1: null, 2: null, foo: null}
m() => {} (empty map) 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} {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} m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
reduce(range(10), put(_a, _, _*_); _a, m()) 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} => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
</pre> </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 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. 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. 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, 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 destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
properties. 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,'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, 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[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> </pre>
### `without_updates(expr)` ### `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 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 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`. 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 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 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 `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. 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 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. 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. 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`. 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 for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
</pre> </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 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 `d` arguments are specified, expands selection by the respective 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 `p` arguments are specified, it uses `d` for negative offset, and `p` for positive. 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 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. (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 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. 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 ## 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. 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 item_category('stone') => building_blocks
</pre> </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)` ### `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 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_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_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand) __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_attacks_entity(player, entity)
__on_player_takes_damage(player, amount, source, source_entity) __on_player_takes_damage(player, amount, source, source_entity)
__on_player_deals_damage(player, amount, 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`. 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` - 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. 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 * `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` - 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 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 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. 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)` ### `print(expr)`
Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. 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. content, or deletes the resource.
Resource is identified by a path to the file. 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 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. 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`. 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` - 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. 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 * `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` - 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 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 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. 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)` ### `print(expr)`
Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. 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. content, or deletes the resource.
Resource is identified by a path to the file. 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 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. 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 * `picked_up`: items picked up
* `dropped`: items dropped * `dropped`: items dropped
* `killed`: mobs killed * `killed`: mobs killed
* `killed_by`: blocks mined * `killed_by`: mobs killed by
* `custom`: various random stats * `custom`: various random stats
For the options of `entry`, consult your statistics page, or give it a guess. 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`. 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 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 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 `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. 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 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. 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. 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`. 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 for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
</pre> </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 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 `d` arguments are specified, expands selection by the respective 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 `p` arguments are specified, it uses `d` for negative offset, and `p` for positive. 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 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. (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 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. 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. 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, 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 destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
properties. 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,'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, 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[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> </pre>
### `without_updates(expr)` ### `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 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 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 Returns a valid string to be used in commands to address an entity. Its UUID for all entities except
player, where its their name. 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> <pre>
run('/kill ' + e~'command_name'); run('/kill ' + e~'command_name');
</pre> </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. 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)` ### `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 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. 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 ## 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. 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_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_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand) __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_attacks_entity(player, entity)
__on_player_takes_damage(player, amount, source, source_entity) __on_player_takes_damage(player, amount, source, source_entity)
__on_player_deals_damage(player, amount, 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 item_category('stone') => building_blocks
</pre> </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)` ### `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 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 ## 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 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 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 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 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> <pre>
l(1,2,'foo') => [1, 2, foo] l(1,2,'foo') <=> [1, 2, 'foo']
l() => [] (empty list) l() <=> [] (empty list)
l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l(1, 2) = l(3, 4) => Error: l is not a variable [1, 2] = [3, 4] => Error: l is not a variable
l(foo, bar) = l(3,4); foo==3 && bar==4 => 1 [foo, bar] = [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] [foo, bar, baz] = [2, 4, 6]; [min(foo, bar), baz] = [3, 5]; [foo, bar, baz] => [3, 4, 5]
</pre> </pre>
In the last example `l(min(foo, bar), baz)` creates a valid L-value, as min(foo, bar) finds the lower of the 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 variables (in this case `foo`) creating a valid assignable L-list of `[foo, baz]`, and these values
will be assigned new values will be assigned new values
### `join(delim, list), join(delim, 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] range(20, 10, -2) => [20, 18, 16, 14, 12]
</pre> </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 ## Map operations
Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with `null` values as sets. 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: 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 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 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 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> <pre>
m(1,2,'foo') => {1: null, 2: null, foo: null} {1, 2, 'foo'} => {1: null, 2: null, foo: null}
m() => {} (empty map) 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} {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} m(l(1, 2), l(3, 4)) <=> {1 -> 2, 3 -> 4} => {1: 2, 3: 4}
reduce(range(10), put(_a, _, _*_); _a, m()) 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} => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
</pre> </pre>
@@ -47,6 +47,8 @@ a() -> global_list+=1; global_list = l(1,2,3); a(); a(); global_list // => [1,2
### `Operator ->` ### `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 To organize code better than a flat sequence of operations, one can define functions. Definition is correct
if has the following form 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> </pre>
In case the inner function wants to operate and modify larger objects, lists from the outer scope, but not global, 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)` ### `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 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 designed to provide the ability to write custom programs to run within Minecraft
and interact with the world. 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 This specification is divided into two sections: this one is agnostic to any
Minecraft related features and could function on its own, and CarpetExpression Minecraft related features and could function on its own, and CarpetExpression
for Minecraft specific routines and world manipulation functions. 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) check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
</pre> </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 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. 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, 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> <pre>
print('foo') => results in foo, prints: foo 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)) print(str('this took %d milliseconds',time()-start_time))
</pre> </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)` ### `profile_expr(expression)`
Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your code. 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 `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 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` 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 Strings also use backslashes `\` for quoting special characters, in both plain strings and regular expressions
<pre> <pre>
@@ -12,6 +12,7 @@ blocks_daylight
blocks_movement blocks_movement
brightness brightness
chunk_tickets chunk_tickets
crafting_remaining_item
create_marker create_marker
current_dimension current_dimension
day_time day_time
@@ -67,6 +68,7 @@ power
property property
query query
random_tick random_tick
recipe_data
rect rect
reload_chunk reload_chunk
remove_all_markers remove_all_markers
@@ -6,6 +6,7 @@ __on_player_attacks_entity
__on_player_breaks_block __on_player_breaks_block
__on_player_clicks_block __on_player_clicks_block
__on_player_changes_dimension __on_player_changes_dimension
__on_player_chooses_recipe
__on_player_deploys_elytra __on_player_deploys_elytra
__on_player_drops_item __on_player_drops_item
__on_player_drops_stack __on_player_drops_stack
@@ -21,6 +22,7 @@ __on_player_starts_sneaking
__on_player_starts_sprinting __on_player_starts_sprinting
__on_player_stops_sneaking __on_player_stops_sneaking
__on_player_stops_sprinting __on_player_stops_sprinting
__on_player_switches_slot
__on_player_uses_item __on_player_uses_item
__on_player_wakes_up __on_player_wakes_up
__on_statistic __on_statistic
@@ -62,7 +62,9 @@
player spawn entity_id entity_list entity_area entity_selector query modify entity_event 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 scan volume neighbours rect diamond
@@ -87,6 +89,7 @@
__on_player_breaks_block __on_player_breaks_block
__on_player_clicks_block __on_player_clicks_block
__on_player_changes_dimension __on_player_changes_dimension
__on_player_chooses_recipe
__on_player_deploys_elytra __on_player_deploys_elytra
__on_player_drops_item __on_player_drops_item
__on_player_drops_stack __on_player_drops_stack
@@ -102,6 +105,7 @@
__on_player_starts_sprinting __on_player_starts_sprinting
__on_player_stops_sneaking __on_player_stops_sneaking
__on_player_stops_sprinting __on_player_stops_sprinting
__on_player_switches_slot
__on_player_uses_item __on_player_uses_item
__on_player_wakes_up __on_player_wakes_up
__on_statistic __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 # or better: https://modmuss50.me/fabric.html
minecraft_version=1.15.2 minecraft_version=1.15.2
yarn_mappings=1.15.2+build.17 yarn_mappings=1.15.2+build.17
loader_version=0.8.7+build.201 loader_version=0.8.8+build.202
# Mod Properties # Mod Properties
mod_version = 1.3.29+v200611 mod_version = 1.4.0+v200623
maven_group = carpet maven_group = carpet
archives_base_name = fabric-carpet archives_base_name = fabric-carpet
+10 -1
View File
@@ -16,11 +16,18 @@ public interface CarpetExtension
default void onGameStarted() {} 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 * Can be loaded multiple times in SinglePlayer
*/ */
default void onServerLoaded(MinecraftServer server) {} 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 * Runs once per game tick, as a first thing in the tick
*/ */
@@ -55,4 +62,6 @@ public interface CarpetExtension
default void registerLoggers() {} default void registerLoggers() {}
default Map<String, String> canHasTranslations(String lang) { return null;} 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); e.onServerLoaded(server);
}); });
scriptServer = new CarpetScriptServer(server); scriptServer = new CarpetScriptServer(server);
scriptServer.loadAllWorldScripts();
MobAI.resetTrackers(); MobAI.resetTrackers();
LoggerRegistry.initLoggers(); LoggerRegistry.initLoggers();
//TickSpeed.reset(); //TickSpeed.reset();
} }
public static void onServerLoadedWorlds(MinecraftServer minecraftServer)
{
extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer));
scriptServer.loadAllWorldScripts();
}
public static void tick(MinecraftServer server) public static void tick(MinecraftServer server)
{ {
TickSpeed.tick(server); TickSpeed.tick(server);
+1 -1
View File
@@ -34,7 +34,7 @@ import static carpet.settings.RuleCategory.CLIENT;
@SuppressWarnings("CanBeFinal") @SuppressWarnings("CanBeFinal")
public class CarpetSettings 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 final Logger LOG = LogManager.getLogger();
public static boolean skipGenerationChecks = false; public static boolean skipGenerationChecks = false;
public static boolean impendingFillSkipUpdates = 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); GoalSelector getAI(boolean target);
Map<String, Goal> getTemporaryTasks(); 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")) @Inject(method = "sendChatMessage", at = @At("HEAD"))
private void inspectMessage(String string, CallbackInfo ci) 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()) if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet())
{ {
ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this; 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); CarpetServer.tick((MinecraftServer) (Object) this);
} }
// Dedicated server only
@Inject(method = "loadWorld", at = @At("HEAD")) @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) 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); 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")) @Inject(method = "shutdown", at = @At("HEAD"))
private void serverClosed(CallbackInfo ci) private void serverClosed(CallbackInfo ci)
{ {
@@ -39,7 +39,7 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
{ {
timeReference = field_4557 = Util.getMeasuringTimeMs(); timeReference = field_4557 = Util.getMeasuringTimeMs();
tick(isAhead); tick(isAhead);
runTask(); while(runTask()) {Thread.yield();}
} }
@Inject(method = "tick", at = @At( @Inject(method = "tick", at = @At(
@@ -4,6 +4,7 @@ import carpet.helpers.TickSpeed;
import carpet.utils.CarpetProfiler; import carpet.utils.CarpetProfiler;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTask; import net.minecraft.server.ServerTask;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Util; import net.minecraft.util.Util;
import net.minecraft.util.profiler.DisableableProfiler; import net.minecraft.util.profiler.DisableableProfiler;
import net.minecraft.util.thread.ReentrantThreadExecutor; import net.minecraft.util.thread.ReentrantThreadExecutor;
@@ -50,6 +51,8 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
@Shadow private volatile boolean loading; @Shadow private volatile boolean loading;
@Shadow public abstract Iterable<ServerWorld> getWorlds();
CarpetProfiler.ProfilerToken currentSection; CarpetProfiler.ProfilerToken currentSection;
private float carpetMsptAccum = 0.0f; private float carpetMsptAccum = 0.0f;
@@ -120,16 +123,16 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
this.profiler.startTick(); this.profiler.startTick();
this.profiler.push("tick"); 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"); 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_19249 = true;
this.field_19248 = Math.max(Util.getMeasuringTimeMs() + /*50L*/ msThisTick, this.timeReference); this.field_19248 = Math.max(Util.getMeasuringTimeMs() + /*50L*/ msThisTick, this.timeReference);
if (TickSpeed.time_warp_start_time != 0) // run all tasks (this will not do a lot when warping), but that's fine since we already run them
{ this.method_16208();
runTasks();
runTasks(() -> !hasRunningTasks() );
}
else { this.method_16208(); }
this.profiler.pop(); this.profiler.pop();
this.profiler.endTick(); this.profiler.endTick();
this.loading = true; 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( @Inject(method = "tick", at = @At(
value = "INVOKE", value = "INVOKE",
target = "Lnet/minecraft/server/PlayerManager;saveAllPlayerData()V", 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 targetSelector;
@Shadow @Final protected GoalSelector goalSelector; @Shadow @Final protected GoalSelector goalSelector;
@Shadow private boolean persistent;
public final Map<String, Goal> temporaryTasks = new HashMap<>(); public final Map<String, Goal> temporaryTasks = new HashMap<>();
@Override @Override
@@ -30,5 +31,9 @@ public abstract class MobEntityMixin implements MobEntityInterface
return temporaryTasks; 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; package carpet.mixins;
import net.minecraft.item.ItemStack; 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.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; 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_RIDES;
import static carpet.script.CarpetEventServer.Event.PLAYER_JUMPS; 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_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_SNEAKING;
import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SPRINTING; 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_SNEAKING;
import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SPRINTING; 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_USES_ITEM;
import static carpet.script.CarpetEventServer.Event.PLAYER_WAKES_UP; 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 //todo add hit and hand in the future
PLAYER_ATTACKS_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), null); 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; package carpet.network;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.utils.ShapesRenderer; import carpet.script.utils.ShapesRenderer;
import net.minecraft.client.network.ClientPlayerEntity; 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; import net.minecraft.util.Identifier;
public class CarpetClient public class CarpetClient
@@ -51,4 +56,26 @@ public class CarpetClient
{ {
return isServerCarpet; 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) if (CarpetClient.shapes != null)
CarpetClient.shapes.addShapes((ListTag) t); 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); 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 carpet.settings.ParsedRule;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.PacketByteBuf; import net.minecraft.util.PacketByteBuf;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
public class ServerNetworkHandler public class ServerNetworkHandler
{ {
private static Map<ServerPlayerEntity, String> remoteCarpetPlayers = new HashMap<>(); private static Map<ServerPlayerEntity, String> remoteCarpetPlayers = new HashMap<>();
private static Set<ServerPlayerEntity> validCarpetPlayers = new HashSet<>(); 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) public static void handleData(PacketByteBuf data, ServerPlayerEntity player)
{ {
if (data != null) if (data != null)
@@ -28,8 +42,13 @@ public class ServerNetworkHandler
int id = data.readVarInt(); int id = data.readVarInt();
if (id == CarpetClient.HELLO) if (id == CarpetClient.HELLO)
onHello(player, data); onHello(player, data);
if (id == CarpetClient.DATA)
onClientData(player, data);
} }
} }
public static void onPlayerJoin(ServerPlayerEntity playerEntity) public static void onPlayerJoin(ServerPlayerEntity playerEntity)
{ {
if (!playerEntity.networkHandler.connection.isLocal()) if (!playerEntity.networkHandler.connection.isLocal())
@@ -60,6 +79,67 @@ public class ServerNetworkHandler
CarpetServer.settingsManager.getRules().forEach(data::withRule); CarpetServer.settingsManager.getRules().forEach(data::withRule);
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() )); 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) public static void updateRuleWithConnectedClients(ParsedRule<?> rule)
{ {
@@ -488,6 +488,37 @@ public class CarpetEventServer
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); 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) PLAYER_TAKES_DAMAGE("player_takes_damage", 4, false)
{ {
@Override @Override
@@ -647,6 +678,8 @@ public class CarpetEventServer
public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { } public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, DimensionType fromDim, DimensionType dimTo) {} public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, DimensionType fromDim, DimensionType dimTo) {}
public void onDamage(Entity target, float amount, DamageSource source) { } 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) { } public void onWorldEvent(ServerWorld world, BlockPos pos) { }
+344 -111
View File
@@ -2,8 +2,10 @@ package carpet.script;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.fakes.ChunkTicketManagerInterface; import carpet.fakes.ChunkTicketManagerInterface;
import carpet.fakes.IngredientInterface;
import carpet.fakes.MinecraftServerInterface; import carpet.fakes.MinecraftServerInterface;
import carpet.fakes.BiomeArrayInterface; import carpet.fakes.BiomeArrayInterface;
import carpet.fakes.RecipeManagerInterface;
import carpet.fakes.ServerChunkManagerInterface; import carpet.fakes.ServerChunkManagerInterface;
import carpet.fakes.ServerLightingProviderInterface; import carpet.fakes.ServerLightingProviderInterface;
import carpet.fakes.StatTypeInterface; import carpet.fakes.StatTypeInterface;
@@ -81,6 +83,13 @@ import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.predicate.entity.EntityPredicates; 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.Scoreboard;
import net.minecraft.scoreboard.ScoreboardCriterion; import net.minecraft.scoreboard.ScoreboardCriterion;
import net.minecraft.scoreboard.ScoreboardObjective; import net.minecraft.scoreboard.ScoreboardObjective;
@@ -97,6 +106,7 @@ import net.minecraft.stat.Stat;
import net.minecraft.stat.StatType; import net.minecraft.stat.StatType;
import net.minecraft.state.StateManager; import net.minecraft.state.StateManager;
import net.minecraft.state.property.Property; import net.minecraft.state.property.Property;
import net.minecraft.structure.StructureFeatures;
import net.minecraft.structure.StructurePiece; import net.minecraft.structure.StructurePiece;
import net.minecraft.structure.StructureStart; import net.minecraft.structure.StructureStart;
import net.minecraft.text.LiteralText; 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.Direction;
import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.BlockBox; import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.Vec3i; import net.minecraft.util.math.Vec3i;
import net.minecraft.util.registry.Registry; 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.Feature;
import net.minecraft.world.gen.feature.StructureFeature; import net.minecraft.world.gen.feature.StructureFeature;
import net.minecraft.world.poi.PointOfInterest; import net.minecraft.world.poi.PointOfInterest;
@@ -130,8 +142,10 @@ import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.gen.ChunkRandom; import net.minecraft.world.gen.ChunkRandom;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.Date;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@@ -342,6 +356,18 @@ public class CarpetExpression
return MapValue.wrap(ret); 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() private void API_BlockManipulation()
{ {
this.expr.addLazyFunction("block", -1, (c, t, lv) -> this.expr.addLazyFunction("block", -1, (c, t, lv) ->
@@ -737,43 +763,76 @@ public class CarpetExpression
this.expr.addLazyFunction("without_updates", 1, (c, t, lv) -> this.expr.addLazyFunction("without_updates", 1, (c, t, lv) ->
{ {
boolean previous = CarpetSettings.impendingFillSkipUpdates; 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; try
Value ret = lv.get(0).evalValue(c, t); {
return (cc, tt) -> ret; CarpetSettings.impendingFillSkipUpdates = true;
} result[0] = lv.get(0).evalValue(c, t);
finally }
{ finally
CarpetSettings.impendingFillSkipUpdates = previous; {
} CarpetSettings.impendingFillSkipUpdates = previous;
}
});
return (cc, tt) -> result[0];
}); });
this.expr.addLazyFunction("set", -1, (c, t, lv) -> this.expr.addLazyFunction("set", -1, (c, t, lv) ->
{ {
CarpetContext cc = (CarpetContext)c; CarpetContext cc = (CarpetContext)c;
ServerWorld world = cc.s.getWorld(); 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 targetLocator = BlockArgument.findIn(cc, lv, 0);
BlockArgument sourceLocator = BlockArgument.findIn(cc, lv, targetLocator.offset, true); BlockArgument sourceLocator = BlockArgument.findIn(cc, lv, targetLocator.offset, true);
BlockState sourceBlockState = sourceLocator.block.getBlockState(); BlockState sourceBlockState = sourceLocator.block.getBlockState();
BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos()); 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(); List<Value> args = new ArrayList<>();
for (int i = sourceLocator.offset; i < lv.size(); i += 2) 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); Property<?> property = states.getProperty(paramString);
if (property == null) if (property == null)
throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString()); 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); 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) if (sourceBlockState == targetBlockState && data == null)
return (c_, t_) -> Value.FALSE; return (c_, t_) -> Value.FALSE;
@@ -783,12 +842,12 @@ public class CarpetExpression
{ {
Clearable.clear(world.getBlockEntity(targetPos)); Clearable.clear(world.getBlockEntity(targetPos));
world.setBlockState(targetPos, finalSourceBlockState, 2); world.setBlockState(targetPos, finalSourceBlockState, 2);
if (data != null) if (finalData != null)
{ {
BlockEntity be = world.getBlockEntity(targetPos); BlockEntity be = world.getBlockEntity(targetPos);
if (be != null) if (be != null)
{ {
CompoundTag destTag = data.copy(); CompoundTag destTag = finalData.copy();
destTag.putInt("x", targetPos.getX()); destTag.putInt("x", targetPos.getX());
destTag.putInt("y", targetPos.getY()); destTag.putInt("y", targetPos.getY());
destTag.putInt("z", targetPos.getZ()); destTag.putInt("z", targetPos.getZ());
@@ -897,7 +956,7 @@ public class CarpetExpression
Tag outtag = tool.getTag(); Tag outtag = tool.getTag();
if (outtag == null) if (outtag == null)
return LazyValue.TRUE; return LazyValue.TRUE;
Value ret = new NBTSerializableValue(outtag); Value ret = new NBTSerializableValue(() -> outtag);
return (_c, _t) -> ret; return (_c, _t) -> ret;
}); });
@@ -1096,6 +1155,9 @@ public class CarpetExpression
BlockArgument locator = BlockArgument.findIn(cc, lv, 0); BlockArgument locator = BlockArgument.findIn(cc, lv, 0);
ServerWorld world = cc.s.getWorld(); ServerWorld world = cc.s.getWorld();
BooYah(world.getChunkManager().getChunkGenerator());
BlockPos pos = locator.block.getPos(); BlockPos pos = locator.block.getPos();
StructureFeature<?> structure = null; StructureFeature<?> structure = null;
boolean needSize = false; boolean needSize = false;
@@ -1385,13 +1447,14 @@ public class CarpetExpression
}); });
this.expr.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> { 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; CarpetContext cc = (CarpetContext)c;
Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard();
String objectiveName = lv.get(0).evalValue(c).getString(); String objectiveName = lv.get(0).evalValue(c).getString();
ScoreboardObjective objective = scoreboard.getObjective(objectiveName); ScoreboardObjective objective = scoreboard.getObjective(objectiveName);
if (objective == null) if (objective == null)
return LazyValue.FALSE; return LazyValue.FALSE;
if (lv.size() == 0) if (lv.size() == 1)
{ {
scoreboard.removeObjective(objective); scoreboard.removeObjective(objective);
return LazyValue.TRUE; return LazyValue.TRUE;
@@ -1472,6 +1535,120 @@ public class CarpetExpression
return (_c, _t) -> res; 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) -> this.expr.addLazyFunction("inventory_size", -1, (c, t, lv) ->
{ {
CarpetContext cc = (CarpetContext) c; CarpetContext cc = (CarpetContext) c;
@@ -1825,11 +2002,9 @@ public class CarpetExpression
Value nbt = lv.get(position.offset).evalValue(c); Value nbt = lv.get(position.offset).evalValue(c);
NBTSerializableValue v = (nbt instanceof NBTSerializableValue) ? (NBTSerializableValue) nbt NBTSerializableValue v = (nbt instanceof NBTSerializableValue) ? (NBTSerializableValue) nbt
: NBTSerializableValue.parseString(nbt.getString()); : NBTSerializableValue.parseString(nbt.getString());
if (v != null) hasTag = true;
{ tag = v.getCompoundTag();
hasTag = true;
tag = v.getCompoundTag();
}
} }
tag.putString("id", entityId.toString()); tag.putString("id", entityId.toString());
Vec3d vec3d = position.vec; Vec3d vec3d = position.vec;
@@ -2097,21 +2272,28 @@ public class CarpetExpression
return (c_, t_) -> new NumericValue(finalSCount); 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(); CarpetContext cc = (CarpetContext)c;
int yi = (int) NumericValue.asNumber(lv.get(1).evalValue(c)).getLong();
int zi = (int) NumericValue.asNumber(lv.get(2).evalValue(c)).getLong(); BlockArgument pos1Locator = BlockArgument.findIn(cc, lv, 0);
int xj = (int) NumericValue.asNumber(lv.get(3).evalValue(c)).getLong(); BlockArgument pos2Locator = BlockArgument.findIn(cc, lv, pos1Locator.offset);
int yj = (int) NumericValue.asNumber(lv.get(4).evalValue(c)).getLong(); BlockPos pos1 = pos1Locator.block.getPos();
int zj = (int) NumericValue.asNumber(lv.get(5).evalValue(c)).getLong(); BlockPos pos2 = pos2Locator.block.getPos();
int minx = min(xi, xj);
int miny = min(yi, yj); int x1 = pos1.getX();
int minz = min(zi, zj); int y1 = pos1.getY();
int maxx = max(xi, xj); int z1 = pos1.getZ();
int maxy = max(yi, yj); int x2 = pos2.getX();
int maxz = max(zi, zj); int y2 = pos2.getY();
LazyValue expr = lv.get(6); 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 //saving outer scope
LazyValue _x = c.getVariable("_x"); LazyValue _x = c.getVariable("_x");
@@ -2181,57 +2363,63 @@ public class CarpetExpression
this.expr.addLazyFunction("rect", -1, (c, t, lv)-> 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"); Vector3Argument diffLocator = Vector3Argument.findIn(cc, lv, cposLocator.offset);
} if (diffLocator.fromBlock)
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
{ {
sminx = 1; sminx = MathHelper.floor(abs(diffLocator.vec.x - cx));
sminy = 1; sminy = MathHelper.floor(abs(diffLocator.vec.y - cx));
sminz = 1; sminz = MathHelper.floor(abs(diffLocator.vec.z - cx));
smaxx = 1;
smaxy = 1;
smaxz = 1;
} }
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; smaxx = sminx;
smaxy = sminy; smaxy = sminy;
smaxz = sminz; 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() return (c_, t_) -> new LazyListValue()
{ {
final int minx = cx-sminx; final int minx = cx-sminx;
@@ -2240,6 +2428,7 @@ public class CarpetExpression
final int maxx = cx+smaxx; final int maxx = cx+smaxx;
final int maxy = cy+smaxy; final int maxy = cy+smaxy;
final int maxz = cz+smaxz; final int maxz = cz+smaxz;
int x; int x;
int y; int y;
int z; int z;
@@ -2299,10 +2488,9 @@ public class CarpetExpression
this.expr.addLazyFunction("diamond", -1, (c, t, lv)-> this.expr.addLazyFunction("diamond", -1, (c, t, lv)->
{ {
CarpetContext cc = (CarpetContext)c; CarpetContext cc = (CarpetContext)c;
if (lv.size() != 3 && lv.size() != 4 && lv.size() != 5)
{ BlockArgument cposLocator=BlockArgument.findIn((CarpetContext)c,lv,0);
throw new InternalExpressionException("'diamond' region should be specified with 3 to 5 coordinates"); BlockPos cpos = cposLocator.block.getPos();
}
int cx; int cx;
int cy; int cy;
@@ -2311,10 +2499,11 @@ public class CarpetExpression
int height; int height;
try try
{ {
cx = (int)((NumericValue) lv.get(0).evalValue(c)).getLong(); cx = cpos.getX();
cy = (int)((NumericValue) lv.get(1).evalValue(c)).getLong(); cy = cpos.getY();
cz = (int)((NumericValue) lv.get(2).evalValue(c)).getLong(); cz = cpos.getZ();
if (lv.size()==3)
if (lv.size()==cposLocator.offset)
{ {
Value retval = ListValue.of( Value retval = ListValue.of(
BlockValue.fromCoords(cc, cx, cy-1, cz), BlockValue.fromCoords(cc, cx, cy-1, cz),
@@ -2327,20 +2516,22 @@ public class CarpetExpression
); );
return (_c, _t ) -> retval; 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; height = 0;
} }
else // size == 5 else if(lv.size()==2+cposLocator.offset)
{ {
width = (int) ((NumericValue) lv.get(3).evalValue(c)).getLong(); width = (int) ((NumericValue) lv.get(cposLocator.offset).evalValue(c)).getLong();
height = (int) ((NumericValue) lv.get(4).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) 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) if (height == 0)
{ {
@@ -2480,7 +2671,7 @@ public class CarpetExpression
public static String recognizeResource(Value value) public static String recognizeResource(Value value)
{ {
String origfile = value.getString(); 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("/")); file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
if (file.isEmpty()) if (file.isEmpty())
{ {
@@ -2756,12 +2947,7 @@ public class CarpetExpression
}); });
this.expr.addLazyFunction("nbt", 1, (c, t, lv) -> { this.expr.addLazyFunction("nbt", 1, (c, t, lv) -> {
Value v = lv.get(0).evalValue(c); Value ret = NBTSerializableValue.fromValue(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;
return (cc, tt) -> ret; return (cc, tt) -> ret;
}); });
@@ -2772,11 +2958,50 @@ public class CarpetExpression
return (cc, tt) -> ret; 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 //"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; ServerCommandSource s = ((CarpetContext)c).s;
Value res = lv.get(0).evalValue(c); 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) if (res instanceof FormattedTextValue)
{ {
s.sendFeedback(((FormattedTextValue) res).getText(), false); s.sendFeedback(((FormattedTextValue) res).getText(), false);
@@ -2792,7 +3017,8 @@ public class CarpetExpression
Messenger.m(s, "w " + res.getString()); 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) -> { this.expr.addLazyFunction("format", -1, (c, t, lv) -> {
@@ -2862,9 +3088,14 @@ public class CarpetExpression
BlockPos target = ((CarpetContext)c).origin; BlockPos target = ((CarpetContext)c).origin;
Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D); Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D);
ServerCommandSource s = ((CarpetContext)c).s; ServerCommandSource s = ((CarpetContext)c).s;
Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute( try
s.withPosition(posf).withSilent().withLevel(CarpetSettings.runPermissionLevel), lv.get(0).evalValue(c).getString())); {
return (c_, t_) -> retval; 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) -> { this.expr.addLazyFunction("save", 0, (c, t, lv) -> {
@@ -2945,7 +3176,7 @@ public class CarpetExpression
this.expr.addLazyFunction("seed", -1, (c, t, lv) -> { this.expr.addLazyFunction("seed", -1, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s; 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; return (cc, tt) -> ret;
}); });
@@ -3013,10 +3244,12 @@ public class CarpetExpression
default: default:
throw new InternalExpressionException("Incorrect dimension string: "+dimString); throw new InternalExpressionException("Incorrect dimension string: "+dimString);
} }
} };
((CarpetContext) c).s = innerSource; if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1);
Value retval = lv.get(1).evalValue(c); Context newCtx = c.recreate();
((CarpetContext) c).s = outerSource; ((CarpetContext) newCtx).s = innerSource;
newCtx.variables = c.variables;
Value retval = lv.get(1).evalValue(newCtx);
return (cc, tt) -> retval; 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 SIGNATURE = 7;
public static final int LOCALIZATION = 8; public static final int LOCALIZATION = 8;
public static final int LVALUE = 9; 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; 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.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.IllegalFormatException; import java.util.IllegalFormatException;
import java.util.Iterator; import java.util.Iterator;
@@ -490,6 +492,11 @@ public class Expression
//assigns const procedure to the lhs, returning its previous value //assigns const procedure to the lhs, returning its previous value
addLazyBinaryOperatorWithDelegation("->", precedence.get("def->"), false, (c, type, e, t, lv1, lv2) -> 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); Value v1 = lv1.evalValue(c, Context.SIGNATURE);
if (!(v1 instanceof FunctionSignatureValue)) if (!(v1 instanceof FunctionSignatureValue))
throw new InternalExpressionException("'->' operator requires a function signature on the LHS"); throw new InternalExpressionException("'->' operator requires a function signature on the LHS");
@@ -1333,11 +1340,18 @@ public class Expression
return LazyListValue.range(from, to, step); 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) if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
return new MapValue(((LazyListValue) lv.get(0)).unroll()); ret = new MapValue(((LazyListValue) lv.get(0)).unroll());
return new MapValue(lv); else
ret = new MapValue(lv);
return (cc, tt) -> ret;
}); });
addUnaryFunction("keys", v -> { addUnaryFunction("keys", v -> {
@@ -1449,20 +1463,6 @@ public class Expression
return (cc, tt) -> ret; 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) -> addLazyFunction("put", -1, (c, t, lv) ->
{ {
if(lv.size()<2) if(lv.size()<2)
@@ -1790,6 +1790,66 @@ public class Expression
return (cc, tt) -> time; 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) -> addLazyFunction("profile_expr", 1, (c, t, lv) ->
{ {
LazyValue lazy = lv.get(0); LazyValue lazy = lv.get(0);
@@ -2000,7 +2060,7 @@ public class Expression
Tokenizer tokenizer = new Tokenizer(c, this, expression, allowComments, allowNewlineSubstitutions); Tokenizer tokenizer = new Tokenizer(c, this, expression, allowComments, allowNewlineSubstitutions);
// stripping lousy but acceptable semicolons // stripping lousy but acceptable semicolons
List<Tokenizer.Token> cleanedTokens = tokenizer.fixSemicolons(); List<Tokenizer.Token> cleanedTokens = tokenizer.postProcess();
Tokenizer.Token lastFunction = null; Tokenizer.Token lastFunction = null;
Tokenizer.Token previousToken = null; Tokenizer.Token previousToken = null;
+50 -6
View File
@@ -43,7 +43,7 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
this.newLinesMarkers = allowNewLineMakers; this.newLinesMarkers = allowNewLineMakers;
} }
public List<Token> fixSemicolons() public List<Token> postProcess()
{ {
Iterable<Token> iterable = () -> this; Iterable<Token> iterable = () -> this;
List<Token> originalTokens = StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); 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) while (originalTokens.size() > 0)
{ {
Token current = originalTokens.remove(originalTokens.size()-1); Token current = originalTokens.remove(originalTokens.size()-1);
if (current.type == Token.TokenType.MARKER && current.surface.startsWith("//"))
continue;
// skipping comments
if (!isSemicolon(current) if (!isSemicolon(current)
|| (last != null && last.type != Token.TokenType.CLOSE_PAREN && last.type != Token.TokenType.COMMA && !isSemicolon(last))) || (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.surface = ";";
current.type = Token.TokenType.OPERATOR; 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); cleanedTokens.add(current);
} }
if (current.type != Token.TokenType.MARKER) if (!(current.type == Token.TokenType.MARKER && current.surface.equals("$")))
last = current; last = current;
} }
Collections.reverse(cleanedTokens); Collections.reverse(cleanedTokens);
@@ -242,7 +263,8 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
} }
token.type = ch == '(' ? Token.TokenType.FUNCTION : Token.TokenType.VARIABLE; 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 == '(') if (ch == '(')
{ {
@@ -252,17 +274,21 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
{ {
token.type = Token.TokenType.CLOSE_PAREN; token.type = Token.TokenType.CLOSE_PAREN;
} }
else else if (ch == ',')
{ {
token.type = Token.TokenType.COMMA; token.type = Token.TokenType.COMMA;
} }
else
{
token.type = Token.TokenType.MARKER;
}
token.append(ch); token.append(ch);
pos++; pos++;
linepos++; linepos++;
if (expression != null && context != null && previousToken != null && if (expression != null && context != null && previousToken != null &&
previousToken.type == Token.TokenType.OPERATOR && previousToken.type == Token.TokenType.OPERATOR &&
(ch == ')' || ch == ',') && (ch == ')' || ch == ',' || ch == ']' || ch == '}') &&
!previousToken.surface.equalsIgnoreCase(";") !previousToken.surface.equalsIgnoreCase(";")
) )
throw new ExpressionException(context, this.expression, previousToken, 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 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.surface += "u";
token.type = Token.TokenType.UNARY_OPERATOR; token.type = Token.TokenType.UNARY_OPERATOR;
@@ -376,6 +404,22 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
public int pos; public int pos;
public int linepos; public int linepos;
public int lineno; 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) public void append(char c)
{ {
@@ -39,16 +39,13 @@ public class ShapesRenderer
{ {
this.client = minecraftClient; this.client = minecraftClient;
shapes = new HashMap<>(); 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) public void render(Camera camera, float partialTick)
{ {
IWorld iWorld = this.client.world; IWorld iWorld = this.client.world;
DimensionType dimensionType = iWorld.getDimension().getType(); 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(); long currentTime = client.world.getTime();
RenderSystem.disableTexture(); RenderSystem.disableTexture();
RenderSystem.enableDepthTest(); RenderSystem.enableDepthTest();
@@ -59,8 +56,7 @@ public class ShapesRenderer
//RenderSystem.shadeModel(7425); //RenderSystem.shadeModel(7425);
RenderSystem.shadeModel(GL11.GL_FLAT); RenderSystem.shadeModel(GL11.GL_FLAT);
RenderSystem.enableAlphaTest(); RenderSystem.enableAlphaTest();
RenderSystem.defaultAlphaFunc(); RenderSystem.alphaFunc(GL11.GL_GREATER, 0.003f);
//RenderSystem.alphaFunc(GL11.GL_GREATER, 0.005F);
RenderSystem.disableCull(); RenderSystem.disableCull();
RenderSystem.disableLighting(); RenderSystem.disableLighting();
RenderSystem.depthMask(false); RenderSystem.depthMask(false);
@@ -131,7 +127,7 @@ public class ShapesRenderer
long key = rshape.key(); long key = rshape.key();
synchronized (shapes) synchronized (shapes)
{ {
RenderedShape<?> existing = shapes.get(dim).get(key); RenderedShape<?> existing = shapes.computeIfAbsent(dim, d -> new Long2ObjectOpenHashMap<>()).get(key);
if (existing != null) if (existing != null)
{ // promoting previous shape { // promoting previous shape
existing.expiryTick = rshape.expiryTick; existing.expiryTick = rshape.expiryTick;
@@ -12,7 +12,10 @@ import net.minecraft.block.Blocks;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.property.Property;
import net.minecraft.util.GlobalPos; import net.minecraft.util.GlobalPos;
import net.minecraft.util.Hand; import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.BlockHitResult;
@@ -24,8 +27,10 @@ import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.chunk.WorldChunk;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -180,6 +185,33 @@ public class BlockValue extends Value
public ServerWorld getWorld() { return world;} 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 { public enum SpecificDirection {
UP("up",0.5, 0.0, 0.5, Direction.UP), UP("up",0.5, 0.0, 0.5, Direction.UP),
@@ -1,6 +1,5 @@
package carpet.script.value; package carpet.script.value;
import carpet.CarpetSettings;
import carpet.fakes.EntityInterface; import carpet.fakes.EntityInterface;
import carpet.fakes.ItemEntityInterface; import carpet.fakes.ItemEntityInterface;
import carpet.fakes.LivingEntityInterface; import carpet.fakes.LivingEntityInterface;
@@ -12,10 +11,10 @@ import carpet.script.CarpetContext;
import carpet.script.EntityEventsGroup; import carpet.script.EntityEventsGroup;
import carpet.script.argument.Vector3Argument; import carpet.script.argument.Vector3Argument;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.utils.Messenger;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException; 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.EntityPassengersSetS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
@@ -61,6 +60,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -87,11 +87,11 @@ public class EntityValue extends Value
EntitySelector entitySelector = selectorCache.get(selector); EntitySelector entitySelector = selectorCache.get(selector);
if (entitySelector != null) if (entitySelector != null)
{ {
return entitySelector.getEntities(source); return entitySelector.getEntities(source.withMaxLevel(4));
} }
entitySelector = new EntitySelectorReader(new StringReader(selector), true).read(); entitySelector = new EntitySelectorReader(new StringReader(selector), true).read();
selectorCache.put(selector, entitySelector); selectorCache.put(selector, entitySelector);
return entitySelector.getEntities(source); return entitySelector.getEntities(source.withMaxLevel(4));
} }
catch (CommandSyntaxException e) catch (CommandSyntaxException e)
{ {
@@ -289,6 +289,10 @@ public class EntityValue extends Value
put("sneaking", (e, a) -> e.isSneaking()?Value.TRUE:Value.FALSE); put("sneaking", (e, a) -> e.isSneaking()?Value.TRUE:Value.FALSE);
put("sprinting", (e, a) -> e.isSprinting()?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("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) -> { put("hunger", (e, a) -> {
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getHungerManager().getFoodLevel()); if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getHungerManager().getFoodLevel());
return Value.NULL; return Value.NULL;
@@ -520,6 +524,10 @@ public class EntityValue extends Value
{ {
throw new InternalExpressionException("'modify' for '"+what+"' expects a 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) 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); e.setCustomName(null);
return; 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())); 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("dismount", (e, v) -> e.stopRiding() );
put("mount", (e, v) -> { put("mount", (e, v) -> {
if (v instanceof EntityValue) 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()); if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong());
}); });
// gamemode [check] put("nbt", (e, v) -> {
// spectate [check] if (!(e instanceof PlayerEntity))
// "fire" [check] {
// "extinguish" [set fire ticks to 0] UUID uUID = e.getUuid();
// "silent" [check] Value tagValue = NBTSerializableValue.fromValue(v);
// "gravity" [check] if (tagValue instanceof NBTSerializableValue)
// "invulnerable" [check] {
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" [] // "dimension" []
// "item" [] // "item" []
// "count", [] // "count", []
// "age", [check]
// "effect_"name [] // "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) 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); throw new InternalExpressionException("Unknown entity event: " + eventName);
((EntityInterface)entity).getEventContainer().addEvent(event, cc, fun, args); ((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; package carpet.script.value;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.text.BaseText; import net.minecraft.text.BaseText;
import net.minecraft.text.Text;
public class FormattedTextValue extends StringValue public class FormattedTextValue extends StringValue
{ {
@@ -37,4 +40,11 @@ public class FormattedTextValue extends StringValue
{ {
return text; 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; package carpet.script.value;
import net.minecraft.nbt.Tag;
public abstract class FrameworkValue extends Value public abstract class FrameworkValue extends Value
{ {
@Override @Override
@@ -25,4 +27,10 @@ public abstract class FrameworkValue extends Value
{ {
throw new UnsupportedOperationException("Scarpet language component cannot be used as map key"); 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.InternalExpressionException;
import carpet.script.exception.ReturnStatement; import carpet.script.exception.ReturnStatement;
import carpet.script.exception.ThrowStatement; import carpet.script.exception.ThrowStatement;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -94,6 +96,8 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
return name.hashCode()+(int)variant; return name.hashCode()+(int)variant;
} }
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {
@@ -231,4 +235,11 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
{ {
return args; 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; package carpet.script.value;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.Tag;
public class GlobalValue extends Value 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"); 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; package carpet.script.value;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
@@ -152,4 +154,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
{ {
return ("i"+getString()).hashCode(); 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.LazyValue;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.item.ItemStack; 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 net.minecraft.util.registry.Registry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@@ -98,6 +108,13 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
{ {
return ListValue.wrap(Arrays.asList(list)); 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() public static LazyValue lazyEmpty()
{ {
@@ -447,4 +464,65 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
{ {
return items.hashCode(); 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; package carpet.script.value;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -226,4 +228,16 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa
{ {
return map.hashCode(); 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.BlockPos;
import net.minecraft.util.math.Box; import net.minecraft.util.math.Box;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -48,6 +49,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
private String nbtString = null; private String nbtString = null;
private Tag nbtTag = null; private Tag nbtTag = null;
private Supplier<Tag> nbtSupplier = null; private Supplier<Tag> nbtSupplier = null;
private boolean owned = false;
private NBTSerializableValue() {} private NBTSerializableValue() {}
@@ -64,11 +66,18 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
throw new InternalExpressionException("Incorrect NBT data: "+nbtString); throw new InternalExpressionException("Incorrect NBT data: "+nbtString);
} }
}; };
owned = true;
} }
public NBTSerializableValue(Tag tag) public NBTSerializableValue(Tag tag)
{ {
nbtTag = tag; nbtTag = tag;
owned = true;
}
public NBTSerializableValue(Supplier<Tag> tagSupplier)
{
nbtSupplier = tagSupplier;
} }
public static Value fromStack(ItemStack stack) public static Value fromStack(ItemStack stack)
@@ -100,7 +109,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
} }
catch (CommandSyntaxException e) catch (CommandSyntaxException e)
{ {
return null; throw new InternalExpressionException("Incorrect NBT tag: nbtString");
} }
NBTSerializableValue value = new NBTSerializableValue(tag); NBTSerializableValue value = new NBTSerializableValue(tag);
value.nbtString = nbtString; value.nbtString = nbtString;
@@ -115,6 +124,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
NBTSerializableValue copy = new NBTSerializableValue(nbtTag); NBTSerializableValue copy = new NBTSerializableValue(nbtTag);
copy.nbtSupplier = this.nbtSupplier; copy.nbtSupplier = this.nbtSupplier;
copy.nbtString = this.nbtString; copy.nbtString = this.nbtString;
copy.owned = this.owned;
return copy; return copy;
} }
@@ -122,9 +132,8 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
public Value deepcopy() public Value deepcopy()
{ {
NBTSerializableValue copy = (NBTSerializableValue) clone(); NBTSerializableValue copy = (NBTSerializableValue) clone();
// same fields except when tag is set - need to copy it. copy.owned = false;
if (copy.nbtTag != null) ensureOwnership();
copy.nbtTag = copy.getTag().copy();
return copy; return copy;
} }
@@ -290,7 +299,50 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
private static Value decodeTag(Tag t) private static Value decodeTag(Tag t)
{ {
if (t instanceof CompoundTag) 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) if (t instanceof AbstractNumberTag)
return new NumericValue(((AbstractNumberTag) t).getDouble()); return new NumericValue(((AbstractNumberTag) t).getDouble());
// more can be done here // more can be done here
@@ -304,13 +356,6 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
return nbtTag; return nbtTag;
} }
private void replaceTag(Tag newTag)
{
nbtTag = newTag;
nbtString = null;
nbtSupplier = null; // just to be sure
}
@Override @Override
public boolean equals(final Object o) public boolean equals(final Object o)
{ {
@@ -346,6 +391,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
{ {
try try
{ {
ensureOwnership();
return (CompoundTag) getTag(); return (CompoundTag) getTag();
} }
catch (ClassCastException e) catch (ClassCastException e)
@@ -364,12 +410,12 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
public boolean put(Value where, Value value, Value conditions) public boolean put(Value where, Value value, Value conditions)
{ {
/// WIP /// WIP
ensureOwnership();
NbtPathArgumentType.NbtPath path = cachePath(where.getString()); NbtPathArgumentType.NbtPath path = cachePath(where.getString());
Tag tagToInsert = value instanceof NBTSerializableValue ? Tag tagToInsert = value instanceof NBTSerializableValue ?
((NBTSerializableValue) value).getTag() : ((NBTSerializableValue) value).getTag() :
new NBTSerializableValue(value.getString()).getTag(); new NBTSerializableValue(value.getString()).getTag();
Tag modifiedTag; boolean modifiedTag;
if (conditions instanceof NumericValue) if (conditions instanceof NumericValue)
{ {
modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert); modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert);
@@ -390,22 +436,17 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
return false; return false;
} }
} }
if (modifiedTag != null) return modifiedTag;
{
replaceTag(modifiedTag);
return true;
}
return false;
} }
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; Collection<Tag> targets;
try try
@@ -414,7 +455,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
} }
catch (CommandSyntaxException e) catch (CommandSyntaxException e)
{ {
return null; return false;
} }
boolean modified = false; boolean modified = false;
@@ -428,27 +469,27 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
{ {
AbstractListTag<?> targetList = (AbstractListTag) target; AbstractListTag<?> targetList = (AbstractListTag) target;
if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy())) if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy()))
return null; return false;
modified = true; modified = true;
} }
catch (IndexOutOfBoundsException ignored) 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)) if (!(replacement instanceof CompoundTag))
{ {
return getTag(); return false;
} }
Tag originalTag = getTag().copy(); Tag ownTag = getTag();
try try
{ {
for (Tag target : nbtPath.getOrInit(originalTag, CompoundTag::new)) for (Tag target : nbtPath.getOrInit(ownTag, CompoundTag::new))
{ {
if (!(target instanceof CompoundTag)) if (!(target instanceof CompoundTag))
{ {
@@ -457,23 +498,26 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
((CompoundTag) target).copyFrom((CompoundTag) replacement); ((CompoundTag) target).copyFrom((CompoundTag) replacement);
} }
} }
catch (CommandSyntaxException ignored) { } catch (CommandSyntaxException ignored)
return originalTag; {
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(); String pathText = nbtPath.toString();
if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement
{ {
if (nbtPath.remove(originalTag)==0) if (nbtPath.remove(tag)==0)
return null; return false;
Pattern pattern = Pattern.compile("\\[[^\\[]*]$"); Pattern pattern = Pattern.compile("\\[[^\\[]*]$");
Matcher matcher = pattern.matcher(pathText); Matcher matcher = pattern.matcher(pathText);
if (!matcher.find()) // malformed path if (!matcher.find()) // malformed path
{ {
return null; return false;
} }
String arrAccess = matcher.group(); String arrAccess = matcher.group();
int pos; int pos;
@@ -487,21 +531,21 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
} }
catch (NumberFormatException e) catch (NumberFormatException e)
{ {
return null; return false;
} }
} }
NbtPathArgumentType.NbtPath newPath = cachePath(pathText.substring(0, pathText.length()-arrAccess.length())); 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 try
{ {
nbtPath.put(originalTag, () -> replacement); nbtPath.put(tag, () -> replacement);
} }
catch (CommandSyntaxException e) catch (CommandSyntaxException e)
{ {
return null; return false;
} }
return originalTag; return true;
} }
@Override @Override
@@ -527,13 +571,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
return cachePath(where.getString()).count(getTag()) > 0; 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 @Override
public boolean delete(Value where) public boolean delete(Value where)
{ {
NbtPathArgumentType.NbtPath path = cachePath(where.getString()); NbtPathArgumentType.NbtPath path = cachePath(where.getString());
Tag tag = getTag().copy(); ensureOwnership();
int removed = path.remove(tag); int removed = path.remove(getTag());
if (removed > 0) replaceTag(tag);
return removed > 0; return removed > 0;
} }
@@ -584,4 +638,23 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
{ {
return "nbt"; 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; package carpet.script.value;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
public class NullValue extends NumericValue // TODO check nonsingleton code public class NullValue extends NumericValue // TODO check nonsingleton code
{ {
@Override @Override
@@ -50,4 +53,11 @@ public class NullValue extends NumericValue // TODO check nonsingleton code
{ {
return 0; 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; package carpet.script.value;
import carpet.script.exception.InternalExpressionException; 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 org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -204,4 +208,20 @@ public class NumericValue extends Value
{ {
return (int)getLong(); 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; package carpet.script.value;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
public class StringValue extends Value public class StringValue extends Value
{ {
private String str; private String str;
@@ -30,4 +33,10 @@ public class StringValue extends Value
{ {
return "string"; 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.Tokenizer;
import carpet.script.exception.ExpressionException; import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.Tag;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -144,4 +145,10 @@ public class ThreadValue extends Value
locks.clear(); 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; package carpet.script.value;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import net.minecraft.nbt.Tag;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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); 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 writeSettingsToConf(conf.getLeft()); // this may feels weird, but if conf
// is locked, it will never reach this point. // is locked, it will never reach this point.
rule.set(source,stringValue); 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; return 1;
} }
// removes overrides of the default values in the file // removes overrides of the default values in the file
@@ -482,6 +482,7 @@ public class SettingsManager
conf.getLeft().remove(rule.name); conf.getLeft().remove(rule.name);
writeSettingsToConf(conf.getLeft()); writeSettingsToConf(conf.getLeft());
rules.get(rule.name).resetToDefault(source); 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; return 1;
} }
@@ -47,7 +47,8 @@
"ui.tags": "Tags", "ui.tags": "Tags",
"ui.change_permanently": "Change permanently", "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.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" "ui.current_value": "current value"
} }
@@ -1,8 +1,8 @@
{ {
"rule.antiCheatDisabled.name": "移动检测通杀", "rule.antiCheatDisabled.name": "禁用反作弊移动监测",
"rule.antiCheatDisabled.desc": "防止玩家因为“本服务器未启用飞行”,“移动过快”等原因被踢出", "rule.antiCheatDisabled.desc": "防止玩家因为“本服务器未启用飞行”,“移动过快”等原因被踢出",
"rule.carpets.name": "放置地毯执行功能", "rule.carpets.name": "多功能地毯",
"rule.carpets.desc": "通过放置地毯方块快捷执行部分功能", "rule.carpets.desc": "通过放置地毯方块快捷执行部分功能",
"rule.combineXPOrbs.name": "经验球合并", "rule.combineXPOrbs.name": "经验球合并",
@@ -135,7 +135,7 @@
"rule.pushLimit.desc": "自定义活塞推动上限", "rule.pushLimit.desc": "自定义活塞推动上限",
"rule.pushLimit.extra.0": "你必须在1-1024中选取一个值", "rule.pushLimit.extra.0": "你必须在1-1024中选取一个值",
"rule.quasiConnectivity.name": "qC激活开关", "rule.quasiConnectivity.name": "半连接激活开关",
"rule.quasiConnectivity.desc": "活塞,发射器和投掷器在他们上方方块激活时是否响应", "rule.quasiConnectivity.desc": "活塞,发射器和投掷器在他们上方方块激活时是否响应",
"rule.quasiConnectivity.extra.0": "设置为true为开启 设置为false为禁用", "rule.quasiConnectivity.extra.0": "设置为true为开启 设置为false为禁用",
@@ -228,7 +228,8 @@
"ui.tags": "分类", "ui.tags": "分类",
"ui.change_permanently": "永久更改", "ui.change_permanently": "永久更改",
"ui.click_to_keep_the_settings_in_%(conf)s_to_save_across_restarts": "点击此按钮以使此选项保存于%s,重启后依旧生效", "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": "当前的值" "ui.current_value": "当前的值"
} }
@@ -331,7 +331,7 @@ select(num) ->
); );
global_selected_point = if (global_selected_point == selected_point, null, selected_point); global_selected_point = if (global_selected_point == selected_point, null, selected_point);
global_needs_updating = true; global_needs_updating = true;
// no need to _update since path is still valid
); );
// player can also punch the mannequin to select/deselect it // player can also punch the mannequin to select/deselect it
@@ -501,7 +501,7 @@ play() ->
task( _() -> ( task( _() -> (
if (global_playing_path, // we don't want to join_task not to lock it just in case. No need to panic here 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 global_playing_path = false; // cancel current path rendering
sleep(1500); // in case we interrupt previous playbacks sleep(1500);
); );
showing_path = global_showing_path; showing_path = global_showing_path;
hide(); hide();
@@ -244,3 +244,23 @@ __on_player_disconnects(player, reason) ->
print('__on_player_disconnects(player)'); print('__on_player_disconnects(player)');
print('Player '+player+' disconnects because: '+reason) 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; l(from, to) = structure_data;
total_size = _euclidean(from, to); total_size = _euclidean(from, to);
density = max(10, total_size/10); 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'); 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) = _; for (structure_pieces, l(piece, direction, from, to) = _;
r = 255 - floor(128 * _i/config:'max_pieces'); 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/ThreadedAnvilChunkStorage$TicketManager
accessible class net/minecraft/server/world/ServerLightingProvider$Stage accessible class net/minecraft/server/world/ServerLightingProvider$Stage
accessible class net/minecraft/recipe/Ingredient$Entry
+3
View File
@@ -169,6 +169,9 @@
"ServerWorld_scarpetEventMixin", "ServerWorld_scarpetEventMixin",
"HungerManagerMixin_scarpetEventsMixin", "HungerManagerMixin_scarpetEventsMixin",
"RecipeManager_scarpetMixin",
"Ingredient_scarpetMixin",
"PistonBlock_movableTEMixin", "PistonBlock_movableTEMixin",
"PistonBlockEntity_movableTEMixin", "PistonBlockEntity_movableTEMixin",
"World_movableTEMixin", "World_movableTEMixin",
+4 -3
View File
@@ -1,7 +1,7 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"id": "carpet", "id": "carpet",
"version": "1.3.29", "version": "1.4.0",
"name": "Carpet Mod in Fabric", "name": "Carpet Mod in Fabric",
"description": "Carpet made out of fabric", "description": "Carpet made out of fabric",
@@ -13,7 +13,7 @@
"sources": "https://github.com/gnembon/fabric-carpet" "sources": "https://github.com/gnembon/fabric-carpet"
}, },
"license": "GNU LGPL V3", "license": "MIT",
"icon": "assets/carpet/icon.png", "icon": "assets/carpet/icon.png",
"environment": "*", "environment": "*",
@@ -24,7 +24,8 @@
"mixins": [ "mixins": [
"carpet.mixins.json" "carpet.mixins.json"
], ],
"accesswidener": "carpet.accesswidener", "accessWidener" : "carpet.accesswidener",
"depends": { "depends": {
"minecraft": "1.15.x", "minecraft": "1.15.x",
"fabricloader": ">=0.7.1" "fabricloader": ">=0.7.1"