Merge branch 'master' into display-title-additions

This commit is contained in:
replaceitem authored and GitHub committed 2021-04-11 21:21:46 +02:00
commit 396e844dce
88 files changed
+3088 -1017

No files matched your search

+320 -99
View File
@@ -296,8 +296,8 @@ For more information, see: [EvalEx GitHub repository](https://github.com/uklimas
`scarpet` provides a number of constants that can be used literally in scripts
* `null`: nothing, zilch, not even false
* `true`: pure true, or just 1 (one)
* `false`: false truth, or true falsth, 0 (zero) actually
* `true`: pure true, can act as `1`
* `false`: false truth, or true falsth, equals to `0`
* `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14
* `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72
@@ -343,7 +343,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct
1357-5 => 1352
1357-'5' => 137
3*'foo'-'o' => 'fff'
l(1,3,5)+7 => l(8,10,12)
[1,3,5]+7 => [8,10,12]
</pre>
As you can see, values can behave differently when mixed with other types in the same expression.
@@ -403,8 +403,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea
any extra arguments.
<pre>
l(1,2,3) ~ 2 => 1
l(1,2,3) ~ 4 => null
[1,2,3] ~ 2 => 1
[1,2,3] ~ 4 => null
'foobar' ~ 'baz' => null
'foobar' ~ '.b' => 'ob'
@@ -430,10 +430,10 @@ Or an example to find if a player has specific enchantment on a held axe (either
<pre>
global_get_enchantment(p, ench) -> (
$ for(l('main','offhand'),
$ for(['main','offhand'],
$ holds = query(p, 'holds', _);
$ if( holds,
$ l(what, count, nbt) = holds;
$ [what, count, nbt] = holds;
$ if( what ~ '_axe' && nbt ~ ench,
$ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
$ )
@@ -470,8 +470,8 @@ Examples:
'foo'*3 => 'foofoofoo'
'foofoofoo' / 3 => 'foo'
'foofoofoo'-'o' => 'fff'
l(1,2,3)+1 => l(2,3,4)
b = l(100,63,100); b+l(10,0,10) => l(110,63,110)
[1,2,3]+1 => [2,3,4]
b = [100,63,100]; b+[10,0,10] => [110,63,110]
{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
</pre>
@@ -513,11 +513,12 @@ second operand is not necessary, it won't be evaluated, which means one can use
case of success returns first positive operand (`||`) or last one (`&&`).
<pre>
true || false => 1
null || false => 0
null != false || run('kill gnembon') => 1 // gnembon survives
null != false && run('kill gnembon') => 0 // when cheats not allowed
null != false && run('kill gnembon') => 1 // gnembon dies, cheats allowed
true || false => true
null || false => false
false || null => null
null != false || run('kill gnembon') // gnembon survives
null != false && run('kill gnembon') // when cheats not allowed
null != false && run('kill gnembon') // gnembon dies, cheats allowed
</pre>
### `Assignment Operators = <> +=`
@@ -531,11 +532,11 @@ require rewriting of the array anyways.
<pre>
a = 5 => a == 5
l(a,b,c) = l(3,4,5) => a==3, b==4, c==5
l(minx,maxx) = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj)
l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f) => [3,4,5,0,1,2]
a = l(1,2,3); a += 4 => [1,2,3,4]
a = l(1,2,3,4); a = filter(a,_!=2) => [1,3,4]
[a,b,c] = [3,4,5] => a==3, b==4, c==5
[minx,maxx] = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj)
[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f] => [3,4,5,0,1,2]
a = [1,2,3]; a += 4 => [1,2,3,4]
a = [1,2,3,4]; a = filter(a,_!=2) => [1,3,4]
</pre>
### `Unary Operators - +`
@@ -553,12 +554,12 @@ Require a number, flips the sign. One way to assert it's a number is by crashing
flips boolean condition of the expression. Equivalent of `bool(expr)==false`
<pre>
!true => 0
!false => 1
!null => 1
!5 => 0
!l() => 1
!l(null) => 0
!true => false
!false => true
!null => true
!5 => false
!l() => true
!l(null) => false
</pre>
# Arithmetic operations
@@ -696,30 +697,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`.
### `bool(expr)`
Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is
alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other
Returns a boolean context of the expression.
Bool is also interpreting string values as boolean, which is different from other
places where boolean context can be used. This can be used in places where API functions return string values to
represent binary values
represent binary values.
<pre>
bool(pi) => 1
bool(false) => 0
bool('') => 0
bool(l()) => 0
bool(l('')) => 1
bool('foo') => 1
bool('false') => 0
bool('nulL') => 0
if('false',1,0) => 1
bool(pi) => true
bool(false) => false
bool('') => false
bool(l()) => false
bool(l('')) => true
bool('foo') => true
bool('false') => false
bool('nulL') => false
if('false',1,0) => true
</pre>
### `number(expr)`
Returns a numeric context of the expression. Can be used to read numbers from strings
Returns a numeric context of the expression. Can be used to read numbers from strings, or other types
<pre>
number(null) => null
number(null) => 0
number(false) => 0
number(true) => 1
number('') => null
number('3.14') => 3.14
number(l()) => 0
@@ -747,15 +749,15 @@ Supported types (with `"%<?>"` syntax):
* `%%`: '%' character
<pre>
str(null) => null
str(false) => 0
str('') => null
str('3.14') => 3.14
str(l()) => 0
str(l('')) => 1
str('foo') => null
str('3bar') => null
str(2)+str(2) => 22
str(null) => 'null'
str(false) => 'false'
str('') => ''
str('3.14') => '3.14'
str([]) => '[]'
str(['']) => '[]'
str('foo') => 'foo'
str('3bar') => '3bar'
str(2)+str(2) => '22'
str('pi: %.2f',pi) => 'pi: 3.14'
str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'
</pre>
@@ -908,18 +910,24 @@ length('foo') => 3
### `rand(expr), rand(expr, seed)`
returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions,
boolean functions, or `bool`), returns false if the randomly selected value is less than 1\. This means
that `rand(2)` returns true half of the time and `rand(5)` returns true for 80% (4/5) of the time. If seed is not
provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the
same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number
(per app), then your random sequence will revert to the beginning.
boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means
that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not
provided, uses a random seed that's shared across all scarpet apps.
If seed is provided, each consecutive call to rand() will act like 'next' call to the
same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app),
so if you exceed this number, your random sequences will revert to the beginning and start over.
<pre>
map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false]
map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
</pre>
## `reset_seed(seed)`
Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value
indicating if the given seed has been used or not.
### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)`
returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples
@@ -1217,12 +1225,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound
and `continue` have no sense and cannot be used inside of `expr` body.
<pre>
all([1,2,3], check_prime(_)) => 1
all(neighbours(x,y,z), _=='stone') => 1 // if all neighbours of [x, y, z] are stone
map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
all([1,2,3], check_prime(_)) => true
all(neighbours(x,y,z), _=='stone') => true // if all neighbours of [x, y, z] are stone
map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
=> [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]]
// find all 3x3 bedrock structures in the top bedrock layer
map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_);
map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_);
all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8),
pos(_) ) => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]]
// ditto, but requiring at most 7 bedrock block in the 18 blocks below them
@@ -1475,15 +1483,73 @@ returns everywhere, but it would often lead to a messy code.
It terminates entire program passing `expr` as the result of the program execution, or null if omitted.
### `try(expr, catch_expr(_)?) ... throw(value?)`
### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)`
`try` function evaluates expression, and continues further unless `throw` function is called anywhere
inside `expr`. In that case the `catch_expr` is evaluates with `_` set to the argument `throw` was called with.
This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns
null as well This mechanism allows to terminate large portion of a convoluted call stack and continue program
execution. There is only one level of exceptions currently in carpet, so if the inner function also defines
the `try` catchment area, it will received the exception first, but it can technically rethrow the value its
getting for the outer scope. Unhandled throw acts like an exit statement.
`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be
thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form
catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`,
or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first
applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only
the first matching block will be executed.
Catch expressions are evaluated with
`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and
column positions), call stack and local
variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type.
You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching
exceptions is typically much more expensive comparing to not throwing them.
The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid
blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods,
or when an error is outside of the programmers scope, such as problems when reading or decoding files.
This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function:
- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught,
but like everywhere else, doing that sounds like a bad idea.
- `value_exception`: This is the parent for any exception that occurs due to an
incorrect argument value provided to a built-in function
- `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`,
`unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific
errors thrown when a specified internal name does not exist or is invalid.
- `io_exception`: This is the parent for any exception that occurs due to an error handling external data.
- `nbt_error`: Incorrect input/output NBT file.
- `json_error`: Incorrect input/output JSON data.
- `user_exception`: Exception thrown by default with `throw` function.
Synopsis:
<pre>
inner_call() ->
(
aaa = 'booyah';
try(
for (range(10), item_tags('stick'+_*'k'));
,
print(_trace) // not caught, only catching user_exceptions
)
);
outer_call() ->
(
try(
inner_call()
, 'exception', // catching everything
print(_trace)
)
);
</pre>
Producing:
```
{stack: [[<app>, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]}
```
### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)`
Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception`
passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet.
With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try`
statements with custom exceptions.
### `if(cond, expr, cond?, expr?, ..., default?)`
@@ -1518,12 +1584,12 @@ In case to simplify the access with nested objects, you can add chain of address
than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
<pre>
get(l(range(10)), 5) => 5
get(l(range(10)), -1) => 9
get(l(range(10)), 10) => 0
l(range(10)):93 => 3
get([range(10)], 5) => 5
get([range(10)], -1) => 9
get([range(10)], 10) => 0
[range(10)]:93 => 3
get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar') => 3
get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar') => 3
</pre>
### `has(container, address, ...), has(lvalue)`
@@ -1595,13 +1661,13 @@ of the existing elements. Use `replace` to remove and replace existing element.
and merges keys from `value` with the compound tag under the path
<pre>
a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4]
a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6]
a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3]
a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
a = [1, 2, 3]; put(a, 1, 4); a => [1, 4, 3]
a = [1, 2, 3]; put(a, null, 4); a => [1, 2, 3, 4]
a = [1, 2, 3]; put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a => [1, 2, 3, 4, 5, 6]
a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a => [1, 4, 5, 6, 2, 3]
a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = {1,2,3,4}; put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}}
tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
@@ -1682,10 +1748,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t
cannot go back nor track where they are in the sequence (see examples).
<pre>
slice([0,1,2,3,4,5], 1, 3) => [1, 2, 3]
slice([0,1,2,3,4,5], 1, 3) => [1, 2]
slice('foobar', 0, 1) => 'f'
slice('foobar', 3) => 'bar'
slice(range(10), 3, 5) => [3, 4, 5]
slice(range(10), 3, 5) => [3, 4]
slice(range(10), 5) => [5, 6, 7, 8, 9]
r = range(100); [slice(r, 5, 7), slice(r, 1, 3)] => [[5, 6], [8, 9]]
</pre>
@@ -1720,7 +1786,7 @@ Primarily to be used in higher order functions
<pre>
range(10) => [...]
l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(range(10),_a+_, 0) => 45
range(5,10) => [5, 6, 7, 8, 9]
@@ -2077,6 +2143,8 @@ when it was used. Block values passed in various places like `scan` functions, e
its properties are needed. This means that if the block at the location changes before its queried in the program this
might result in getting the later state, which might not be desired. Consider the following example:
Throws `unknown_block` if provided input is not valid.
<pre>set(10,10,10,'stone');
scan(10,10,10,0,0,0, b = _);
set(10,10,10,'air');
@@ -2106,6 +2174,8 @@ properties that the original source block may have contained.
The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
Throws `unknown_block` if provided block to set is not valid
<pre>
set(0,5,0,'bedrock') => bedrock
set(l(0,5,0), 'bedrock') => bedrock
@@ -2184,6 +2254,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l
Returns true if placement/use was
successful, false otherwise.
Throws `unknown_item` if `item` doesn't exist
<pre>
place_item('stone',x,y,z) // places a stone block on x,y,z block
place_item('piston,x,y,z,'down') // places a piston facing down
@@ -2202,6 +2274,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial
markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
Throws `unknown_poi` if the provided point of interest doesn't exist
### `set_biome(pos, biome_name, update=true)`
Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
@@ -2212,6 +2286,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the
specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
noise).
Throws `unknown_biome` if the `biome_name` doesn't exist.
### `update(pos)`
Causes a block update at position.
@@ -2236,6 +2312,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic
considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the
programmer to use that nbt to apply it where it belong
Throws `unknown_item` if `tool` doesn't exist.
Here is a sample code that can be used to mine blocks using items in player inventory, without using player context
for mining. Obviously, in this case the use of `harvest` would be much more applicable:
@@ -2327,6 +2405,8 @@ back in state definition in various applications where block properties are requ
`block_state` can also accept block names as input, returning block's default state.
Throws `unknown_block` if the provided input is not valid.
<pre>
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...}
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top
@@ -2347,6 +2427,8 @@ Without arguments, returns list of available tags, with block supplied (either b
of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong
to this tag, and `true` if the block belongs to the tag.
Throws `unknown_block` if `block` doesn't exist
### `block_data(pos)`
Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently
@@ -2355,16 +2437,18 @@ used to match specific information from it, or use it to copy to another block
<pre> block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }'
</pre>
### `poi(pos), poi(pos, radius?, type?, status?, column?)`
### `poi(pos), poi(pos, radius?, type?, status?, column_search?)`
Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its
occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a
given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`.
If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status.
With `column` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire
With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire
block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius`
radius.
All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`.
The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates.
Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs
@@ -2381,9 +2465,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
Without arguments, returns the list of biomes in the world.
With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid.
With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid.
With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include:
With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include:
* `'top_material'`: unlocalized block representing the top surface material
* `'under_material'`: unlocalized block representing what sits below topsoil
* `'category'`: the parent biome this biome is derived from. Possible values include:
@@ -2772,6 +2856,7 @@ If structure is not specified, it will return a set of structure names that are
as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
should be generated there.
Throws `unknown_structure` if structure doesn't exist.
### `structures(pos), structures(pos, structure_name)`
@@ -2805,6 +2890,8 @@ structure starts.
Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the
structure may depend on the biome, otherwise the default structure for this type will be generated.
Throws `unknown_structure` if structure doesn't exist.
### `plop(pos, what)`
Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates.
@@ -2827,7 +2914,9 @@ All generated structures will retain their properties, like mob spawning, howeve
itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs,
because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress.
### `custom_dimension(name, seed?)`
### (deprecated) `custom_dimension(name, seed?)`
Deprecated by `create_datapack()` which can be used to setup custom dimensions
Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world
generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided).
@@ -3082,7 +3171,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t
### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')`
String of entity name
String of entity name or formatted text in the case of `display_name`
<pre>
query(e,'name') => Leatherworker
@@ -3627,6 +3716,10 @@ Requires a living entity as an argument.
Will make the entity jump once.
### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')`
Makes the living entity swing their required limb.
### `modify(e, 'silent', boolean)`
Silences or unsilences the entity.
@@ -3808,14 +3901,16 @@ an inventory, all API functions typically do nothing and return null.
Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item.
### item_list(tag?)
### `item_list(tag?)`
With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid.
### item_tags(item, tag?)
### `item_tags(item, tag?)`
Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag
Throws `unknown_item` if item doesn't exist.
### `stack_limit(item)`
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
@@ -3823,6 +3918,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function
it is up to the programmer to keep it at bay. As of 1.13, game checks for negative numbers and setting an item to
negative is the same as empty.
Throws `unknown_item` if item doesn't exist.
<pre>
stack_limit('wooden_axe') => 1
stack_limit('ender_pearl') => 16
@@ -3833,6 +3930,8 @@ stack_limit('stone') => 64
Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`.
Throws `unknown_item` if item doesn't exist.
<pre>
item_category('wooden_axe') => tools
item_category('ender_pearl') => misc
@@ -3955,6 +4054,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo
inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
</pre>
Throws `unknown_item` if item doesn't exist.
### `inventory_remove(inventory, item, amount?)`
Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing
@@ -4349,13 +4450,14 @@ Displays or modifies individual scoreboard values. With no arguments, returns th
With specified `objective`, lists all keys (players) associated with current objective, or `null` if objective does not exist.
With specified `objective` and
`key`, returns current value of the objective for a given player (key). With additional `value` sets a new scoreboard
value, returning previous value associated with the `key`.
value, returning previous value associated with the `key`. If the `value` is null, resets the scoreboard value.
### `scoreboard_add(objective, criterion?)`
Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`.
If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion.
If the objective was added, returns `true`. If nothing is affected, returns `null`
Returns `true` if the objective was created, or `null` if an objective with the specified name already exists.
Throws `unknown_criterion` if criterion doesn't exist.
<pre>
scoreboard_add('counter')
@@ -4374,6 +4476,15 @@ for the objective.
Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid,
returns `null`.
### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)`
Reads a property of an `objective` or sets it to a `value` if specified. Available properties are:
* `criterion`
* `display_name` (Formatted text supported)
* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot
* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified
# Team
### `team_list()`, `team_list(team)`
@@ -4512,29 +4623,40 @@ Collection of other methods that control smaller, yet still important aspects of
## Sounds
### `sound(name, pos, volume?, pitch?, mixer?)`
### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)`
Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under
optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`.
Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient`
and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a
corresponding `playsound` command.
Used with no arguments, return the list of available sound names.
Throws `unknown_sound` if sound doesn't exist.
## Particles
### `particle(name, pos, count?. spread?, speed?, player?)`
### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)`
Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed`
of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle`
command on details on those options. Valid particle names are
for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`.
Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid
particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration
to be valid, like `dust`, `block` etc. Should be used as a reference only.
Throws `unknown_particle` if particle doesn't exist.
### `particle_line(name, pos, pos2, density?, player?)`
Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far
apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only
that player will receive particles.
Throws `unknown_particle` if particle doesn't exist.
### `particle_box(name, pos, pos2, density?, player?)`
### `particle_rect` (deprecated)
@@ -4542,6 +4664,8 @@ that player will receive particles.
Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is
supplied, only that player will receive particles.
Throws `unknown_particle` if particle doesn't exist.
## Markers
### `draw_shape(shape, duration, key?, value?, ... )`,
@@ -4575,7 +4699,7 @@ Optional shared shape attributes:
in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`.
* `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified
player, otherwise it will be send to all players in the dimension.
* `line` - line thickness, defaults to 2.0pt
* `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer.
* `fill` - color for the faces, defaults to no fill. Use `color` attribute format
* `follow` - entity, or player name. Shape will follow an entity instead of being static.
Follow attribute requires all positional arguments to be relative to the entity and disallow
@@ -4609,7 +4733,7 @@ Available shapes:
* `align` - text alignment with regards to `pos`. Default is `center` (displayed text is
centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos`
indicates the end of text).
* `tilt` - additional rotation of the text on the canvas
* `tilt`, `lean`, `turn` - additional rotations of the text on the canvas along all three axis
* `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`)
with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which
can be used to display multiple lines of text bound to the same `pos`
@@ -4793,7 +4917,8 @@ Resource is identified by a path to the file.
A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder.
Do not add file extensions to the descriptor - extensions are inferred
based on the `type` of the file.
based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from
zip files, although you cannot nest zip files in other zip files.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning
@@ -4807,7 +4932,7 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s
The default no-name app, via `/script run` command can only save/load/read files from the shared space.
Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true`
Functions return `null` if no file is present (for read, list and delete operations). Returns `true`
for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files
for folder listing.
@@ -4828,6 +4953,14 @@ record to the file. Since files are closed after each write, sending multiple li
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
Throws:
- `nbt_read_error`: When failed to read NBT file.
- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem.
- `io_exception`: For all other errors when handling data on disk not related to encoding issues
All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception
thrown.
<pre>
write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n');
@@ -4879,6 +5012,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
You can use app data to save non-vanilla information separately from the world and other scripts.
Throws `nbt_read_error` if failed to read app data.
### `store_app_data(tag)`
Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
@@ -4893,6 +5028,83 @@ Returns `true` if the file was saved successfully, `false` otherwise.
Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`.
### `create_datapack(name, data)`
Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the
json files of the target pack.
Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made.
Returns `false` if adding of the datapack wasn't successful.
Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in
reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload`
command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`.
Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically.
Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads
dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified
file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects
(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more
experimental `check_hidden_dimensions()` after adding a datapack if needed.
Synopsis:
<pre>
script run create_datapack('foo',
{
'foo' -> {
'bar.json' -> {
'c' -> true,
'd' -> false,
'e' -> {'foo' -> [1,2,3]},
'a' -> 'foobar',
'b' -> 5
}
}
})
script run create_datapack('funky_world', {
'data' -> {
'minecraft' -> {
'dimension' -> {
'custom_ow.json' -> {
'type' -> 'minecraft:the_end',
'generator' -> {
'biome_source' -> {
'seed' -> 0,
'large_biomes' -> false,
'type' -> 'minecraft:vanilla_layered'
},
'seed' -> 0,
'settings' -> 'minecraft:nether',
'type' -> 'minecraft:noise'
}
}
}
}
}
});
check_hidden_dimensions(); => ['funky_world']
</pre>
### `enable_hidden_dimensions()`
The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added
to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the
datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling
`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process.
Fine print: The function should be
considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected
clients will not see suggestions for commands that use dimensions `/execute in <dim>` (vanilla client limitation)
but all commands should work just fine with
the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced.
The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings
for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by
vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to
mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang
themselves, so that's about it.
### `tick_time()`
Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time.
@@ -4956,6 +5168,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
Throws `unknown_dimension` if provided dimension can't be found.
### `view_distance()`
Returns the view distance of the server.
@@ -5010,11 +5224,12 @@ Available options in the scarpet app space:
Relevant world related properties
* `world_name` - name of the world
* `world_seed` - a numeric seed of the world
* `world_dimensions` - a list of dimensions in the world
* `world_path` - full path to the world saves folder
* `world_folder` - name of the direct folder in the saves that holds world files
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
* `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
* `world_spawn_point` - world spawn point
Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`
@@ -5025,7 +5240,13 @@ Available options in the scarpet app space:
* `game_view_distance` - the view distance
* `game_mod_name` - the name of the base mod. Expect `'fabric'`
* `game_version` - base version of the game
* `game_target` - target release version
* `game_major_target` - major release target. For 1.12.2, that would be 12
* `game_minor_reease` - minor release target. For 1.12.2, that woudl be 2
* `game_protocol` - protocol version number
* `game_pack_version` - datapack version number
* `game_data_version` - data version of the game. Returns an integer, so it can be compared.
* `game_stable` - indicating if its a production release or a snapshot
Server related properties
* `server_motd` - the motd of the server visible when joining
+117 -7
View File
@@ -4,29 +4,40 @@ Collection of other methods that control smaller, yet still important aspects of
## Sounds
### `sound(name, pos, volume?, pitch?, mixer?)`
### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)`
Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under
optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`.
Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient`
and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a
corresponding `playsound` command.
Used with no arguments, return the list of available sound names.
Throws `unknown_sound` if sound doesn't exist.
## Particles
### `particle(name, pos, count?. spread?, speed?, player?)`
### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)`
Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed`
of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle`
command on details on those options. Valid particle names are
for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`.
Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid
particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration
to be valid, like `dust`, `block` etc. Should be used as a reference only.
Throws `unknown_particle` if particle doesn't exist.
### `particle_line(name, pos, pos2, density?, player?)`
Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far
apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only
that player will receive particles.
Throws `unknown_particle` if particle doesn't exist.
### `particle_box(name, pos, pos2, density?, player?)`
### `particle_rect` (deprecated)
@@ -34,6 +45,8 @@ that player will receive particles.
Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is
supplied, only that player will receive particles.
Throws `unknown_particle` if particle doesn't exist.
## Markers
### `draw_shape(shape, duration, key?, value?, ... )`,
@@ -67,7 +80,7 @@ Optional shared shape attributes:
in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`.
* `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified
player, otherwise it will be send to all players in the dimension.
* `line` - line thickness, defaults to 2.0pt
* `line` - (Deprecated) line thickness, defaults to 2.0pt. Not supported in 1.17's 3.2 core GL renderer.
* `fill` - color for the faces, defaults to no fill. Use `color` attribute format
* `follow` - entity, or player name. Shape will follow an entity instead of being static.
Follow attribute requires all positional arguments to be relative to the entity and disallow
@@ -101,7 +114,7 @@ Available shapes:
* `align` - text alignment with regards to `pos`. Default is `center` (displayed text is
centered with respect to `pos`), `left` (`pos` indicates beginning of text), and `right` (`pos`
indicates the end of text).
* `tilt` - additional rotation of the text on the canvas
* `tilt`, `lean`, `turn` - additional rotations of the text on the canvas along all three axis
* `indent`, `height`, `raise` - offsets for text rendering on X (`indent`), Y (`height`), and Z axis (`raise`)
with regards to the plane of the text. One unit of these corresponds to 1 line spacing, which
can be used to display multiple lines of text bound to the same `pos`
@@ -296,7 +309,8 @@ Resource is identified by a path to the file.
A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder.
Do not add file extensions to the descriptor - extensions are inferred
based on the `type` of the file.
based on the `type` of the file. A path can have one `'.zip'` component indicating a zip folder allowing to read / write to and from
zip files, although you cannot nest zip files in other zip files.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning
@@ -310,7 +324,7 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s
The default no-name app, via `/script run` command can only save/load/read files from the shared space.
Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true`
Functions return `null` if no file is present (for read, list and delete operations). Returns `true`
for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files
for folder listing.
@@ -331,6 +345,14 @@ record to the file. Since files are closed after each write, sending multiple li
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
Throws:
- `nbt_read_error`: When failed to read NBT file.
- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem.
- `io_exception`: For all other errors when handling data on disk not related to encoding issues
All other errors resulting of improper use of input arguments should result in `null` returned from the function, rather than exception
thrown.
<pre>
write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n');
@@ -382,6 +404,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh
You can use app data to save non-vanilla information separately from the world and other scripts.
Throws `nbt_read_error` if failed to read app data.
### `store_app_data(tag)`
Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
@@ -396,6 +420,83 @@ Returns `true` if the file was saved successfully, `false` otherwise.
Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`.
### `create_datapack(name, data)`
Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the
json files of the target pack.
Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made.
Returns `false` if adding of the datapack wasn't successful.
Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in
reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload`
command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`.
Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically.
Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads
dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified
file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects
(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more
experimental `check_hidden_dimensions()` after adding a datapack if needed.
Synopsis:
<pre>
script run create_datapack('foo',
{
'foo' -> {
'bar.json' -> {
'c' -> true,
'd' -> false,
'e' -> {'foo' -> [1,2,3]},
'a' -> 'foobar',
'b' -> 5
}
}
})
script run create_datapack('funky_world', {
'data' -> {
'minecraft' -> {
'dimension' -> {
'custom_ow.json' -> {
'type' -> 'minecraft:the_end',
'generator' -> {
'biome_source' -> {
'seed' -> 0,
'large_biomes' -> false,
'type' -> 'minecraft:vanilla_layered'
},
'seed' -> 0,
'settings' -> 'minecraft:nether',
'type' -> 'minecraft:noise'
}
}
}
}
}
});
check_hidden_dimensions(); => ['funky_world']
</pre>
### `enable_hidden_dimensions()`
The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added
to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the
datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling
`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process.
Fine print: The function should be
considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected
clients will not see suggestions for commands that use dimensions `/execute in <dim>` (vanilla client limitation)
but all commands should work just fine with
the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced.
The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings
for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by
vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to
mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang
themselves, so that's about it.
### `tick_time()`
Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time.
@@ -459,6 +560,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
Throws `unknown_dimension` if provided dimension can't be found.
### `view_distance()`
Returns the view distance of the server.
@@ -513,11 +616,12 @@ Available options in the scarpet app space:
Relevant world related properties
* `world_name` - name of the world
* `world_seed` - a numeric seed of the world
* `world_dimensions` - a list of dimensions in the world
* `world_path` - full path to the world saves folder
* `world_folder` - name of the direct folder in the saves that holds world files
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
* `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
* `world_spawn_point` - world spawn point
Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peaceful'`, `'easy'`, `'normal'`, or `'hard'`
@@ -528,7 +632,13 @@ Available options in the scarpet app space:
* `game_view_distance` - the view distance
* `game_mod_name` - the name of the base mod. Expect `'fabric'`
* `game_version` - base version of the game
* `game_target` - target release version
* `game_major_target` - major release target. For 1.12.2, that would be 12
* `game_minor_reease` - minor release target. For 1.12.2, that woudl be 2
* `game_protocol` - protocol version number
* `game_pack_version` - datapack version number
* `game_data_version` - data version of the game. Returns an integer, so it can be compared.
* `game_stable` - indicating if its a production release or a snapshot
Server related properties
* `server_motd` - the motd of the server visible when joining
+35 -5
View File
@@ -21,6 +21,8 @@ when it was used. Block values passed in various places like `scan` functions, e
its properties are needed. This means that if the block at the location changes before its queried in the program this
might result in getting the later state, which might not be desired. Consider the following example:
Throws `unknown_block` if provided input is not valid.
<pre>set(10,10,10,'stone');
scan(10,10,10,0,0,0, b = _);
set(10,10,10,'air');
@@ -50,6 +52,8 @@ properties that the original source block may have contained.
The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
Throws `unknown_block` if provided block to set is not valid
<pre>
set(0,5,0,'bedrock') => bedrock
set(l(0,5,0), 'bedrock') => bedrock
@@ -128,6 +132,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l
Returns true if placement/use was
successful, false otherwise.
Throws `unknown_item` if `item` doesn't exist
<pre>
place_item('stone',x,y,z) // places a stone block on x,y,z block
place_item('piston,x,y,z,'down') // places a piston facing down
@@ -146,6 +152,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial
markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
Throws `unknown_poi` if the provided point of interest doesn't exist
### `set_biome(pos, biome_name, update=true)`
Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
@@ -156,6 +164,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the
specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random
noise).
Throws `unknown_biome` if the `biome_name` doesn't exist.
### `update(pos)`
Causes a block update at position.
@@ -180,6 +190,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic
considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the
programmer to use that nbt to apply it where it belong
Throws `unknown_item` if `tool` doesn't exist.
Here is a sample code that can be used to mine blocks using items in player inventory, without using player context
for mining. Obviously, in this case the use of `harvest` would be much more applicable:
@@ -217,6 +229,13 @@ Causes a block to be harvested by a specified player entity. Honors player item
tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break
that block, a block doesn't get destroyed either.
### `create_explosion(pos, power?, mode?, fire?, source?, attacker?)`
Creates an explosion at a given position. Default values of optional parameters are: `'power'` - `4` (TNT power),
`'mode'` (block breaking effect `none`, `destroy` or `break`: `break`, `fire` (whether extra fire blocks should be created) - `false`,
`source` (exploding entity) - `null` and `attacker` (entity responsible for trigerring) - `null`. Explosions created with this
endpoint cannot be captured with `__on_explosion` event, however they will be captured by `__on_explosion_outcome`.
### `weather()`,`weather(type)`,`weather(type, ticks)`
If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
@@ -271,6 +290,8 @@ back in state definition in various applications where block properties are requ
`block_state` can also accept block names as input, returning block's default state.
Throws `unknown_block` if the provided input is not valid.
<pre>
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...}
set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top
@@ -291,6 +312,8 @@ Without arguments, returns list of available tags, with block supplied (either b
of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong
to this tag, and `true` if the block belongs to the tag.
Throws `unknown_block` if `block` doesn't exist
### `block_data(pos)`
Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently
@@ -299,16 +322,18 @@ used to match specific information from it, or use it to copy to another block
<pre> block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }'
</pre>
### `poi(pos), poi(pos, radius?, type?, status?, column?)`
### `poi(pos), poi(pos, radius?, type?, status?, column_search?)`
Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its
occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a
given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`.
If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status.
With `column` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire
With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire
block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius`
radius.
All results of `poi` calls are returned in sorted order with respect to the euclidean distance to the requested center of `pos`.
The return format of the results is a list of poi type, occupancy load, and extra triple of coordinates.
Querying for POIs using the radius is the intended use of POI mechanics, and the ability of accessing individual POIs
@@ -325,9 +350,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
Without arguments, returns the list of biomes in the world.
With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid.
With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid.
With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include:
With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include:
* `'top_material'`: unlocalized block representing the top surface material
* `'under_material'`: unlocalized block representing what sits below topsoil
* `'category'`: the parent biome this biome is derived from. Possible values include:
@@ -716,6 +741,7 @@ If structure is not specified, it will return a set of structure names that are
as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
should be generated there.
Throws `unknown_structure` if structure doesn't exist.
### `structures(pos), structures(pos, structure_name)`
@@ -749,6 +775,8 @@ structure starts.
Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the
structure may depend on the biome, otherwise the default structure for this type will be generated.
Throws `unknown_structure` if structure doesn't exist.
### `plop(pos, what)`
Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates.
@@ -771,7 +799,9 @@ All generated structures will retain their properties, like mob spawning, howeve
itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs,
because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress.
### `custom_dimension(name, seed?)`
### (deprecated) `custom_dimension(name, seed?)`
Deprecated by `create_datapack()` which can be used to setup custom dimensions
Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world
generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided).
+5 -1
View File
@@ -177,7 +177,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t
### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')`
String of entity name
String of entity name or formatted text in the case of `display_name`
<pre>
query(e,'name') => Leatherworker
@@ -722,6 +722,10 @@ Requires a living entity as an argument.
Will make the entity jump once.
### `modify(e, 'swing')` `modify(e, 'swing', 'offhand')`
Makes the living entity swing their required limb.
### `modify(e, 'silent', boolean)`
Silences or unsilences the entity.
+14 -1
View File
@@ -79,6 +79,19 @@ at the same time.
Triggered right after a lightning strikes. Lightning entity as well as potential horseman trap would
already be spawned at that point. `mode` is `true` if the lightning did cause a trap to spawn.
### `__on_explosion(pos, power, source, causer, mode, fire)`
Event triggered right before explosion takes place and before has any effect on the world. `source` can be an entity causing
the explosion, and `causer` the entity triggering it,
`mode` indicates block effects: `'none'`, `'break'` (drop all blocks), or `'destroy'` - drop few blocks. Event
is not captured when `create_explosion()` is called.
### `__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)`
Triggered during the explosion, before any changes to the blocks are done,
but the decision to blow up is already made and entities are already affected.
The parameter `blocks` contains the list of blocks that will blow up (empty if `explosionNoBlockDamage` is set to `true`).
The parameter `entities` contains the list of entities that have been affected by the explosion. Triggered even with `create_explosion()`.
### `__on_carpet_rule_changes(rule, new_value)`
Triggered when a carpet mod rule is changed. It includes extension rules, not using default `/carpet` command,
which will then be namespaced as `namespace:rule`.
@@ -107,7 +120,7 @@ Example events that may cause it to happen is releasing a bow. The event is trig
the request, however the `item_tuple` is provided representing the item that the player started with. You can use that and
compare with the currently held item for a delta.
### `__on_player_finishes_using_item(player, item_tuple, hand))`
### `__on_player_finishes_using_item(player, item_tuple, hand)`
Player using of an item is done. This is controlled server side and is responsible for such events as finishing
eating. The event is triggered after confirming that the action is valid, and sending the feedback back
to the client, but before triggering it and its effects in game.
+10 -2
View File
@@ -28,14 +28,16 @@ an inventory, all API functions typically do nothing and return null.
Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item.
### item_list(tag?)
### `item_list(tag?)`
With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid.
### item_tags(item, tag?)
### `item_tags(item, tag?)`
Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag
Throws `unknown_item` if item doesn't exist.
### `stack_limit(item)`
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
@@ -43,6 +45,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function
it is up to the programmer to keep it at bay. As of 1.13, game checks for negative numbers and setting an item to
negative is the same as empty.
Throws `unknown_item` if item doesn't exist.
<pre>
stack_limit('wooden_axe') => 1
stack_limit('ender_pearl') => 16
@@ -53,6 +57,8 @@ stack_limit('stone') => 64
Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`.
Throws `unknown_item` if item doesn't exist.
<pre>
item_category('wooden_axe') => tools
item_category('ender_pearl') => misc
@@ -175,6 +181,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo
inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items
</pre>
Throws `unknown_item` if item doesn't exist.
### `inventory_remove(inventory, item, amount?)`
Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing
+13 -3
View File
@@ -6,13 +6,14 @@ Displays or modifies individual scoreboard values. With no arguments, returns th
With specified `objective`, lists all keys (players) associated with current objective, or `null` if objective does not exist.
With specified `objective` and
`key`, returns current value of the objective for a given player (key). With additional `value` sets a new scoreboard
value, returning previous value associated with the `key`.
value, returning previous value associated with the `key`. If the `value` is null, resets the scoreboard value.
### `scoreboard_add(objective, criterion?)`
Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`.
If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion.
If the objective was added, returns `true`. If nothing is affected, returns `null`
Returns `true` if the objective was created, or `null` if an objective with the specified name already exists.
Throws `unknown_criterion` if criterion doesn't exist.
<pre>
scoreboard_add('counter')
@@ -31,6 +32,15 @@ for the objective.
Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid,
returns `null`.
### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)`
Reads a property of an `objective` or sets it to a `value` if specified. Available properties are:
* `criterion`
* `display_name` (Formatted text supported)
* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot
* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified
# Team
### `team_list()`, `team_list(team)`
+15 -15
View File
@@ -23,12 +23,12 @@ In case to simplify the access with nested objects, you can add chain of address
than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
<pre>
get(l(range(10)), 5) => 5
get(l(range(10)), -1) => 9
get(l(range(10)), 10) => 0
l(range(10)):93 => 3
get([range(10)], 5) => 5
get([range(10)], -1) => 9
get([range(10)], 10) => 0
[range(10)]:93 => 3
get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar') => 3
get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar') => 3
</pre>
### `has(container, address, ...), has(lvalue)`
@@ -100,13 +100,13 @@ of the existing elements. Use `replace` to remove and replace existing element.
and merges keys from `value` with the compound tag under the path
<pre>
a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4]
a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6]
a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3]
a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
a = [1, 2, 3]; put(a, 1, 4); a => [1, 4, 3]
a = [1, 2, 3]; put(a, null, 4); a => [1, 2, 3, 4]
a = [1, 2, 3]; put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a => [1, 2, 3, 4, 5, 6]
a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a => [1, 4, 5, 6, 2, 3]
a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
a = {1,2,3,4}; put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}}
tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]}
tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
@@ -187,10 +187,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t
cannot go back nor track where they are in the sequence (see examples).
<pre>
slice([0,1,2,3,4,5], 1, 3) => [1, 2, 3]
slice([0,1,2,3,4,5], 1, 3) => [1, 2]
slice('foobar', 0, 1) => 'f'
slice('foobar', 3) => 'bar'
slice(range(10), 3, 5) => [3, 4, 5]
slice(range(10), 3, 5) => [3, 4]
slice(range(10), 5) => [5, 6, 7, 8, 9]
r = range(100); [slice(r, 5, 7), slice(r, 1, 3)] => [[5, 6], [8, 9]]
</pre>
@@ -225,7 +225,7 @@ Primarily to be used in higher order functions
<pre>
range(10) => [...]
l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(range(10),_a+_, 0) => 45
range(5,10) => [5, 6, 7, 8, 9]
@@ -234,15 +234,73 @@ returns everywhere, but it would often lead to a messy code.
It terminates entire program passing `expr` as the result of the program execution, or null if omitted.
### `try(expr, catch_expr(_)?) ... throw(value?)`
### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)`
`try` function evaluates expression, and continues further unless `throw` function is called anywhere
inside `expr`. In that case the `catch_expr` is evaluates with `_` set to the argument `throw` was called with.
This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns
null as well This mechanism allows to terminate large portion of a convoluted call stack and continue program
execution. There is only one level of exceptions currently in carpet, so if the inner function also defines
the `try` catchment area, it will received the exception first, but it can technically rethrow the value its
getting for the outer scope. Unhandled throw acts like an exit statement.
`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be
thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form
catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`,
or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first
applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only
the first matching block will be executed.
Catch expressions are evaluated with
`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and
column positions), call stack and local
variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type.
You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching
exceptions is typically much more expensive comparing to not throwing them.
The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid
blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods,
or when an error is outside of the programmers scope, such as problems when reading or decoding files.
This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function:
- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught,
but like everywhere else, doing that sounds like a bad idea.
- `value_exception`: This is the parent for any exception that occurs due to an
incorrect argument value provided to a built-in function
- `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`,
`unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific
errors thrown when a specified internal name does not exist or is invalid.
- `io_exception`: This is the parent for any exception that occurs due to an error handling external data.
- `nbt_error`: Incorrect input/output NBT file.
- `json_error`: Incorrect input/output JSON data.
- `user_exception`: Exception thrown by default with `throw` function.
Synopsis:
<pre>
inner_call() ->
(
aaa = 'booyah';
try(
for (range(10), item_tags('stick'+_*'k'));
,
print(_trace) // not caught, only catching user_exceptions
)
);
outer_call() ->
(
try(
inner_call()
, 'exception', // catching everything
print(_trace)
)
);
</pre>
Producing:
```
{stack: [[<app>, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]}
```
### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)`
Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception`
passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet.
With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try`
statements with custom exceptions.
### `if(cond, expr, cond?, expr?, ..., default?)`
@@ -124,12 +124,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound
and `continue` have no sense and cannot be used inside of `expr` body.
<pre>
all([1,2,3], check_prime(_)) => 1
all(neighbours(x,y,z), _=='stone') => 1 // if all neighbours of [x, y, z] are stone
map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
all([1,2,3], check_prime(_)) => true
all(neighbours(x,y,z), _=='stone') => true // if all neighbours of [x, y, z] are stone
map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) )
=> [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]]
// find all 3x3 bedrock structures in the top bedrock layer
map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_);
map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_);
all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8),
pos(_) ) => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]]
// ditto, but requiring at most 7 bedrock block in the 18 blocks below them
+24 -23
View File
@@ -16,7 +16,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct
1357-5 => 1352
1357-'5' => 137
3*'foo'-'o' => 'fff'
l(1,3,5)+7 => l(8,10,12)
[1,3,5]+7 => [8,10,12]
</pre>
As you can see, values can behave differently when mixed with other types in the same expression.
@@ -76,8 +76,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea
any extra arguments.
<pre>
l(1,2,3) ~ 2 => 1
l(1,2,3) ~ 4 => null
[1,2,3] ~ 2 => 1
[1,2,3] ~ 4 => null
'foobar' ~ 'baz' => null
'foobar' ~ '.b' => 'ob'
@@ -103,10 +103,10 @@ Or an example to find if a player has specific enchantment on a held axe (either
<pre>
global_get_enchantment(p, ench) -> (
$ for(l('main','offhand'),
$ for(['main','offhand'],
$ holds = query(p, 'holds', _);
$ if( holds,
$ l(what, count, nbt) = holds;
$ [what, count, nbt] = holds;
$ if( what ~ '_axe' && nbt ~ ench,
$ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
$ )
@@ -143,8 +143,8 @@ Examples:
'foo'*3 => 'foofoofoo'
'foofoofoo' / 3 => 'foo'
'foofoofoo'-'o' => 'fff'
l(1,2,3)+1 => l(2,3,4)
b = l(100,63,100); b+l(10,0,10) => l(110,63,110)
[1,2,3]+1 => [2,3,4]
b = [100,63,100]; b+[10,0,10] => [110,63,110]
{'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
</pre>
@@ -186,11 +186,12 @@ second operand is not necessary, it won't be evaluated, which means one can use
case of success returns first positive operand (`||`) or last one (`&&`).
<pre>
true || false => 1
null || false => 0
null != false || run('kill gnembon') => 1 // gnembon survives
null != false && run('kill gnembon') => 0 // when cheats not allowed
null != false && run('kill gnembon') => 1 // gnembon dies, cheats allowed
true || false => true
null || false => false
false || null => null
null != false || run('kill gnembon') // gnembon survives
null != false && run('kill gnembon') // when cheats not allowed
null != false && run('kill gnembon') // gnembon dies, cheats allowed
</pre>
### `Assignment Operators = <> +=`
@@ -204,11 +205,11 @@ require rewriting of the array anyways.
<pre>
a = 5 => a == 5
l(a,b,c) = l(3,4,5) => a==3, b==4, c==5
l(minx,maxx) = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj)
l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f) => [3,4,5,0,1,2]
a = l(1,2,3); a += 4 => [1,2,3,4]
a = l(1,2,3,4); a = filter(a,_!=2) => [1,3,4]
[a,b,c] = [3,4,5] => a==3, b==4, c==5
[minx,maxx] = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj)
[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f] => [3,4,5,0,1,2]
a = [1,2,3]; a += 4 => [1,2,3,4]
a = [1,2,3,4]; a = filter(a,_!=2) => [1,3,4]
</pre>
### `Unary Operators - +`
@@ -226,10 +227,10 @@ Require a number, flips the sign. One way to assert it's a number is by crashing
flips boolean condition of the expression. Equivalent of `bool(expr)==false`
<pre>
!true => 0
!false => 1
!null => 1
!5 => 0
!l() => 1
!l(null) => 0
!true => false
!false => true
!null => true
!5 => false
!l() => true
!l(null) => false
</pre>
+36 -29
View File
@@ -14,30 +14,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`.
### `bool(expr)`
Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is
alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other
Returns a boolean context of the expression.
Bool is also interpreting string values as boolean, which is different from other
places where boolean context can be used. This can be used in places where API functions return string values to
represent binary values
represent binary values.
<pre>
bool(pi) => 1
bool(false) => 0
bool('') => 0
bool(l()) => 0
bool(l('')) => 1
bool('foo') => 1
bool('false') => 0
bool('nulL') => 0
if('false',1,0) => 1
bool(pi) => true
bool(false) => false
bool('') => false
bool(l()) => false
bool(l('')) => true
bool('foo') => true
bool('false') => false
bool('nulL') => false
if('false',1,0) => true
</pre>
### `number(expr)`
Returns a numeric context of the expression. Can be used to read numbers from strings
Returns a numeric context of the expression. Can be used to read numbers from strings, or other types
<pre>
number(null) => null
number(null) => 0
number(false) => 0
number(true) => 1
number('') => null
number('3.14') => 3.14
number(l()) => 0
@@ -65,15 +66,15 @@ Supported types (with `"%<?>"` syntax):
* `%%`: '%' character
<pre>
str(null) => null
str(false) => 0
str('') => null
str('3.14') => 3.14
str(l()) => 0
str(l('')) => 1
str('foo') => null
str('3bar') => null
str(2)+str(2) => 22
str(null) => 'null'
str(false) => 'false'
str('') => ''
str('3.14') => '3.14'
str([]) => '[]'
str(['']) => '[]'
str('foo') => 'foo'
str('3bar') => '3bar'
str(2)+str(2) => '22'
str('pi: %.2f',pi) => 'pi: 3.14'
str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'
</pre>
@@ -226,18 +227,24 @@ length('foo') => 3
### `rand(expr), rand(expr, seed)`
returns a random number from `0.0` (inclusive) to `expr` (exclusive). In boolean context (in conditions,
boolean functions, or `bool`), returns false if the randomly selected value is less than 1\. This means
that `rand(2)` returns true half of the time and `rand(5)` returns true for 80% (4/5) of the time. If seed is not
provided, uses a random seed. If seed is provided, each consecutive call to rand() will act like 'next' call to the
same random object. Scarpet keeps track of up to 1024 custom random number generators, so if you exceed this number
(per app), then your random sequence will revert to the beginning.
boolean functions, or `bool`), returns false if the randomly selected value is less than 1. This means
that `bool(rand(2))` returns true half of the time and `!rand(5)` returns true for 20% (1/5) of the time. If seed is not
provided, uses a random seed that's shared across all scarpet apps.
If seed is provided, each consecutive call to rand() will act like 'next' call to the
same random object. Scarpet keeps track of up to 65536 custom random number generators (custom seeds, per app),
so if you exceed this number, your random sequences will revert to the beginning and start over.
<pre>
map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false]
map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
</pre>
## `reset_seed(seed)`
Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value
indicating if the given seed has been used or not.
### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)`
returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples
@@ -3,8 +3,8 @@
`scarpet` provides a number of constants that can be used literally in scripts
* `null`: nothing, zilch, not even false
* `true`: pure true, or just 1 (one)
* `false`: false truth, or true falsth, 0 (zero) actually
* `true`: pure true, can act as `1`
* `false`: false truth, or true falsth, equals to `0`
* `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14
* `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72
@@ -63,6 +63,7 @@ reduce
relu
replace
replace_first
reset_seed
return
round
sec
+4 -1
View File
@@ -17,9 +17,10 @@ brightness
chunk_tickets
convert_date
crafting_remaining_item
create_datapack
create_explosion
create_marker
current_dimension
custom_dimension
day_time
delete_file
destroy
@@ -28,6 +29,7 @@ display_title
draw_shape
drop_item
emitted_light
enable_hidden_dimensions
encode_nbt
entity_area
entity_event
@@ -96,6 +98,7 @@ scoreboard
scoreboard_add
scoreboard_remove
scoreboard_display
scoreboard_property
tag_matches
team_add
team_leave
@@ -4,6 +4,7 @@ _i
_x
_y
_z
_trace
euler
false
null
+3 -1
View File
@@ -42,4 +42,6 @@ __on_player_deals_damage(player, amount, entity) ->
__on_player_dies(player) ->
__on_player_respawns(player) ->
__on_player_disconnects(player, reason) ->
__on_player_connects(player) ->
__on_player_connects(player) ->
__on_explosion(pos, power, source, causer, mode, fire) ->
__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
+10 -6
View File
@@ -25,7 +25,7 @@
<Keywords name="Folders in comment, middle"></Keywords>
<Keywords name="Folders in comment, close"></Keywords>
<Keywords name="Keywords1">
not fact rand
not fact rand reset_seed
sin cos tan asin acos atan atan2 sinh cosh tanh sec csc sech csch cot acot coth asinh acosh atanh
rad deg
log log10 log1p
@@ -46,7 +46,7 @@
var undef vars if loop map filter first all c_for for while reduce continue break
import call outer type hash_code bool number str length rand sleep time profile_expr lower upper title replace replace_first
import call outer type hash_code bool number str length sleep time profile_expr lower upper title replace replace_first
</Keywords>
<Keywords name="Keywords2">
block pos pos_offset solid air liquid flammable
@@ -55,14 +55,16 @@
set without_updates
blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi
place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets
structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension
plop harvest destroy create_explosion
structure_eligibility structures structure_references set_structure
reset_chunk reload_chunk create_datapack enable_hidden_dimensions
add_chunk_ticket
convert_date unix_time
display_title
scoreboard scoreboard_add scoreboard_remove scoreboard_display
scoreboard scoreboard_add scoreboard_remove scoreboard_display scoreboard_property
team_add team_list team_remove team_leave team_property
@@ -82,7 +84,7 @@
run save tick_time world_time day_time last_tick_times game_tick current_dimension in_dimension view_distance
plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
schedule nbt draw_shape create_marker remove_all_markers
load_app_data store_app_data read_file write_file delete_file list_files
escape_nbt parse_nbt encode_nbt tag_matches statistic
@@ -91,7 +93,7 @@
</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="Keywords5">_ _i _a _x _y _z _trace</Keywords>
<Keywords name="Keywords6">global_</Keywords>
<Keywords name="Keywords7">
__command
@@ -139,6 +141,8 @@
__on_player_respawns
__on_player_disconnects
__on_player_connects
__on_explosion
__on_explosion_outcome
</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>
+1 -1
View File
@@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G
fabric_version=0.29.4+1.16
# Mod Properties
mod_version = 1.4.27
mod_version = 1.4.31
maven_group = carpet
archives_base_name = fabric-carpet
+20 -6
View File
@@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT;
@SuppressWarnings("CanBeFinal")
public class CarpetSettings
{
public static final String carpetVersion = "1.4.27+v210217";
public static final String carpetVersion = "1.4.31+v210407";
public static final Logger LOG = LogManager.getLogger("carpet");
public static boolean skipGenerationChecks = false;
public static boolean impendingFillSkipUpdates = false;
@@ -413,20 +413,26 @@ public class CarpetSettings
private static class ModulePermissionLevel extends Validator<String> {
@Override public String validate(ServerCommandSource source, ParsedRule<String> currentRule, String newValue, String string) {
CarpetSettings.runPermissionLevel = SettingsManager.getCommandLevel(newValue);
int permissionLevel = SettingsManager.getCommandLevel(newValue);
if (source != null && !source.hasPermissionLevel(permissionLevel))
return null;
CarpetSettings.runPermissionLevel = permissionLevel;
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
return newValue;
}
@Override
public String description() { return "Also controls permission level of commands executed via `run()`";}
public String description() { return "When changing the rule, you must at least have the permission level you are trying to give it";}
}
@Rule(
desc = "Enables restrictions for arbitrary code execution with scarpet",
extra = {
"Users that don't have this permission level",
"won't be able to load apps or /script run"
"won't be able to load apps or /script run.",
"It is also the permission level apps will",
"have when running commands with run()"
},
category = {COMMAND, SCARPET},
validate = ModulePermissionLevel.class
category = {SCARPET},
validate = {Validator._COMMAND_LEVEL_VALIDATOR.class, ModulePermissionLevel.class}
)
public static String commandScriptACE = "ops";
@@ -532,6 +538,14 @@ public class CarpetSettings
)
public static int maxEntityCollisions = 0;
@Rule(
desc = "Customizable server list ping (Multiplayer menu) playerlist sample limit",
options = {"0", "12", "20", "40"},
category = CREATIVE,
strict = false,
validate = Validator.NONNEGATIVE_NUMBER.class
)
public static int pingPlayerListLimit = 12;
/*
@Rule(
@@ -57,7 +57,7 @@ public class PerimeterInfoCommand
});
if (!(baseEntity instanceof MobEntity))
{
Messenger.m(source, "r Failed to spawn test entity");
Messenger.m(source, "r /perimeterinfo requires a mob entity to test agains.");
if (baseEntity != null) baseEntity.remove();
return 0;
}
@@ -23,6 +23,7 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
@@ -271,7 +272,13 @@ public class PlayerCommand
Messenger.m(context.getSource(), "rb Player name: "+playerName+" is too long");
return 0;
}
MinecraftServer server = source.getMinecraftServer();
if (!World.isInBuildLimit(new BlockPos(pos.x, pos.y, pos.z)))
{
Messenger.m(context.getSource(), "rb Player "+playerName+" cannot be placed outside of the world");
return 0;
}
PlayerEntity player = EntityPlayerMPFake.createFake(playerName, server, pos.x, pos.y, pos.z, facing.y, facing.x, dimType, mode);
if (player == null)
{
@@ -13,6 +13,7 @@ import carpet.script.exception.CarpetExpressionException;
import carpet.script.value.FunctionValue;
import carpet.script.value.Value;
import carpet.settings.SettingsManager;
import carpet.utils.CarpetProfiler;
import carpet.utils.Messenger;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -396,6 +397,7 @@ public class ScriptCommand
{
try
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet run", CarpetProfiler.TYPE.GENERAL);
host.setChatErrorSnooper(source);
long start = System.nanoTime();
Value result = call.get();
@@ -412,7 +414,9 @@ public class ScriptCommand
metric = "s";
}
Messenger.m(source, "wi = ", "wb "+result.getString(), "gi ("+time+metric+")");
return (int)result.readInteger();
int intres = (int)result.readInteger();
CarpetProfiler.end_current_section(currentSection);
return intres;
}
catch (CarpetExpressionException e)
{
@@ -1,5 +1,6 @@
package carpet.fakes;
import net.minecraft.resource.ServerResourceManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
@@ -13,4 +14,5 @@ public interface MinecraftServerInterface
void forceTick(BooleanSupplier sup);
LevelStorage.Session getCMSession();
Map<RegistryKey<World>, ServerWorld> getCMWorlds();
ServerResourceManager getResourceManager();
}
@@ -10,13 +10,10 @@ import java.util.List;
import carpet.logging.logHelpers.ExplosionLogHelper;
import carpet.mixins.ExplosionAccessor;
import carpet.CarpetSettings;
import carpet.mixins.ExplosionMixin;
import carpet.utils.Messenger;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
@@ -48,6 +45,8 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
public class OptimizedExplosion
{
private static List<Entity> entitylist;
@@ -68,9 +67,14 @@ public class OptimizedExplosion
private static ArrayList<Float> chances = new ArrayList<>();
private static BlockPos blastChanceLocation;
// Creating entity list for scarpet event
private static List<Entity> entityList = new ArrayList<>();
public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) {
ExplosionAccessor eAccess = (ExplosionAccessor) e;
entityList.clear();
boolean eventNeeded = EXPLOSION_OUTCOME.isNeeded() && !eAccess.getWorld().isClient();
blastCalc(e);
if (!CarpetSettings.explosionNoBlockDamage) {
@@ -107,20 +111,22 @@ public class OptimizedExplosion
explosionSound++;
Entity explodingEntity = eAccess.getEntity();
for (int k2 = 0; k2 < entitylist.size(); ++k2) {
Entity entity = entitylist.get(k2);
if (entity == eAccess.getEntity()) {
if (entity == explodingEntity) {
// entitylist.remove(k2);
removeFast(entitylist, k2);
k2--;
continue;
}
if (entity instanceof TntEntity &&
entity.getX() == eAccess.getEntity().getX() &&
entity.getY() == eAccess.getEntity().getY() &&
entity.getZ() == eAccess.getEntity().getZ()) {
if (entity instanceof TntEntity && explodingEntity != null &&
entity.getX() == explodingEntity.getX() &&
entity.getY() == explodingEntity.getY() &&
entity.getZ() == explodingEntity.getZ()) {
if (eLogger != null) {
eLogger.onEntityImpacted(entity, new Vec3d(0,-0.9923437498509884d, 0));
}
@@ -154,6 +160,11 @@ public class OptimizedExplosion
densityCache.put(pair, density);
}
// If it is needed, it saves the entity
if (eventNeeded) {
entityList.add(entity);
}
double d10 = (1.0D - d12) * density;
entity.damage(e.getDamageSource(),
(float) ((int) ((d10 * d10 + d10) / 2.0D * 7.0D * (double) f3 + 1.0D)));
@@ -193,6 +204,11 @@ public class OptimizedExplosion
double posY = eAccess.getY();
double posZ = eAccess.getZ();
// If it is needed, calls scarpet event
if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient()) {
EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, eAccess.getEntity(), e::getCausingEntity, eAccess.getX(), eAccess.getY(), eAccess.getZ(), eAccess.getPower(), eAccess.isCreateFire(), e.getAffectedBlocks(), entityList, eAccess.getDestructionType());
}
boolean damagesTerrain = eAccess.getDestructionType() != Explosion.DestructionType.NONE;
// explosionSound incremented till disabling the explosion particles and sound
@@ -596,4 +612,4 @@ public class OptimizedExplosion
);
}
}
}
}
@@ -74,6 +74,7 @@ public class TickSpeed
if (player_active_timeout < PLAYER_GRACE)
{
player_active_timeout = PLAYER_GRACE;
ServerNetworkHandler.updateTickPlayerActiveTimeoutToConnectedPlayers();
}
}
@@ -1,5 +1,6 @@
package carpet.logging;
import carpet.CarpetServer;
import carpet.helpers.HopperCounter;
import carpet.helpers.TickSpeed;
import carpet.logging.logHelpers.PacketCounter;
@@ -62,7 +63,7 @@ public class HUDController
public static void update_hud(MinecraftServer server)
{
if(server.getTicks() % 20 != 0)
if(server.getTicks() % 20 != 0 || CarpetServer.minecraft_server == null)
return;
player_huds.clear();
@@ -44,9 +44,11 @@ public abstract class CommandManagerMixin
@SuppressWarnings("UnresolvedMixinReference")
@Redirect(method = "execute", at = @At(
value = "INVOKE",
target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z"
))
value = "INVOKE",
target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z"
),
require = 0
)
private boolean doesOutputCommandStackTrace(Logger logger)
{
if (CarpetSettings.superSecretSetting)
@@ -0,0 +1,71 @@
package carpet.mixins;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.explosion.ExplosionBehavior;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static carpet.script.CarpetEventServer.Event.EXPLOSION_OUTCOME;
@Mixin(value = Explosion.class, priority = 990)
public abstract class Explosion_scarpetEventMixin
{
@Shadow @Final private World world;
@Shadow @Final private double x;
@Shadow @Final private double y;
@Shadow @Final private double z;
@Shadow @Final private float power;
@Shadow @Final private boolean createFire;
@Shadow @Final private List<BlockPos> affectedBlocks;
@Shadow @Final private Explosion.DestructionType destructionType;
@Shadow @Final private @Nullable Entity entity;
@Shadow /*@Nullable*/ public abstract /*@Nullable*/ LivingEntity getCausingEntity();
private List<Entity> affectedEntities;
@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;Lnet/minecraft/entity/damage/DamageSource;Lnet/minecraft/world/explosion/ExplosionBehavior;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V",
at = @At(value = "RETURN"))
private void onExplosionCreated(World world, Entity entity, DamageSource damageSource, ExplosionBehavior explosionBehavior, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType destructionType, CallbackInfo ci)
{
if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
{
affectedEntities = new ArrayList<>();
}
}
@Inject(method = "collectBlocksAndDamageEntities", locals= LocalCapture.CAPTURE_FAILHARD, at=@At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;damage(Lnet/minecraft/entity/damage/DamageSource;F)Z"))
private void onExplosion(CallbackInfo ci, Set<BlockPos> s1, float f1, int i1, int i2, int i3, int i4, int i5, int i6, List<Entity> l1, Vec3d v1, int i7, Entity entity)
{
if (affectedEntities != null)
{
affectedEntities.add(entity);
}
}
@Inject(method = "affectWorld", at = @At("HEAD"))
private void onExplosion(boolean spawnParticles, CallbackInfo ci)
{
if (EXPLOSION_OUTCOME.isNeeded() && !world.isClient())
{
EXPLOSION_OUTCOME.onExplosion((ServerWorld) world, entity, this::getCausingEntity, x, y, z, power, createFire, affectedBlocks, affectedEntities, destructionType);
}
}
}
@@ -2,6 +2,7 @@ package carpet.mixins;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.utils.CarpetProfiler;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.WorldGenerationProgressListener;
import org.spongepowered.asm.mixin.Mixin;
@@ -26,7 +27,9 @@ public abstract class MinecraftServer_coreMixin
)
)
private void onTick(BooleanSupplier booleanSupplier_1, CallbackInfo ci) {
CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Carpet", CarpetProfiler.TYPE.GENERAL);
CarpetServer.tick((MinecraftServer) (Object) this);
CarpetProfiler.end_current_section(token);
}
@Inject(method = "loadWorld", at = @At("HEAD"))
@@ -0,0 +1,19 @@
package carpet.mixins;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import carpet.CarpetSettings;
import net.minecraft.server.MinecraftServer;
@Mixin(MinecraftServer.class)
public abstract class MinecraftServer_pingPlayerSampleLimit
{
@ModifyConstant(method = "tick", constant = @Constant(intValue = 12), require = 0, allow = 1)
private int modifyPlayerSampleLimit(int value)
{
return CarpetSettings.pingPlayerListLimit;
}
}
@@ -2,6 +2,7 @@ package carpet.mixins;
import carpet.fakes.MinecraftServerInterface;
import carpet.helpers.TickSpeed;
import net.minecraft.resource.ServerResourceManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTask;
import net.minecraft.server.world.ServerWorld;
@@ -44,6 +45,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
@Shadow @Final private Map<RegistryKey<World>, ServerWorld> worlds;
@Shadow private ServerResourceManager serverResourceManager;
@Override
public void forceTick(BooleanSupplier isAhead)
{
@@ -58,6 +61,11 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut
return session;
}
@Override
public ServerResourceManager getResourceManager() {
return serverResourceManager;
}
@Override
public Map<RegistryKey<World>, ServerWorld> getCMWorlds()
{
@@ -208,7 +208,8 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
{
if (CarpetProfiler.tick_health_requested != 0L)
{
CarpetProfiler.end_tick_profiling((MinecraftServer) (Object)this, currentSection);
CarpetProfiler.end_current_section(currentSection);
CarpetProfiler.end_tick_profiling((MinecraftServer) (Object)this);
}
}
@@ -25,10 +25,4 @@ public class PlayerManager_coreMixin
CarpetServer.onPlayerLoggedIn(player);
PLAYER_CONNECTS.onPlayerEvent(player);
}
@Inject(method = "onDataPacksReloaded", at = @At("HEAD"))
private void onResourcesRefreshedReloadCommand(CallbackInfo ci)
{
CarpetServer.onReload(server);
}
}
@@ -1,11 +1,13 @@
package carpet.mixins;
import carpet.CarpetServer;
import carpet.fakes.ServerPlayerEntityInterface;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static carpet.script.CarpetEventServer.Event.PLAYER_RESPAWNS;
@@ -25,4 +27,9 @@ public class PlayerManager_scarpetEventsMixin
((ServerPlayerEntityInterface)player).invalidateEntityObjectReference();
}
@Inject(method = "onDataPacksReloaded", at = @At("HEAD"))
private void reloadCommands(CallbackInfo ci)
{
CarpetServer.scriptServer.reAddCommands();
}
}
@@ -0,0 +1,23 @@
package carpet.mixins;
import carpet.CarpetServer;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.command.ReloadCommand;
import net.minecraft.server.command.ServerCommandSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ReloadCommand.class)
public class ReloadCommand_reloadAppsMixin {
//method_13530(Lcom/mojang/brigadier/context/CommandContext;)I
// internal of register.
@SuppressWarnings("UnresolvedMixinReference")
@Inject(method = "method_13530", at = @At("TAIL"))
private static void onReload(CommandContext<ServerCommandSource> context, CallbackInfoReturnable<Integer> cir)
{
// can't fetch here the reference to the server
CarpetServer.onReload(context.getSource().getMinecraftServer());
}
}
@@ -5,20 +5,26 @@ import carpet.fakes.ServerWorldInterface;
import carpet.script.CarpetEventServer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LightningEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.explosion.ExplosionBehavior;
import net.minecraft.world.level.ServerWorldProperties;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
import static carpet.script.CarpetEventServer.Event.EXPLOSION;
import static carpet.script.CarpetEventServer.Event.LIGHTNING;
@Mixin(ServerWorld.class)
@@ -57,6 +63,14 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
}
}
@Inject(method = "createExplosion", at = @At("HEAD"))
private void handleExplosion(/*@Nullable*/ Entity entity, /*@Nullable*/ DamageSource damageSource, /*@Nullable*/ ExplosionBehavior explosionBehavior, double d, double e, double f, float g, boolean bl, Explosion.DestructionType destructionType, CallbackInfoReturnable<Explosion> cir)
{
if (EXPLOSION.isNeeded())
EXPLOSION.onExplosion((ServerWorld) (Object)this, entity, null, d, e, f, g, bl, null, null, destructionType);
}
@Final
@Shadow
private ServerWorldProperties worldProperties;
public ServerWorldProperties getWorldPropertiesCM(){
@@ -6,11 +6,11 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(targets="net.minecraft.client.sound.SoundEngine$SourceSetImpl")
@Mixin(targets="net.minecraft.client.sound.SoundEngine$SourceSetImpl", priority = 69420)
public class SoundEngineInt_cleanLogsMixin
{
// createSource // method_19666, remap = false,
@Redirect(method = "createSource()Lnet/minecraft/client/sound/Source;", at = @At(remap = false,
@Redirect(method = "createSource()Lnet/minecraft/client/sound/Source;", require = 0, at = @At(remap = false,
value="INVOKE",
target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V"
))
@@ -7,10 +7,10 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(SoundSystem.class)
@Mixin(value = SoundSystem.class, priority = 69420)
public class SoundSystem_cleanLogsMixin
{
@Redirect(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", at = @At( remap = false,
@Redirect(method = "play(Lnet/minecraft/client/sound/SoundInstance;)V", require = 0, at = @At( remap = false,
value = "INVOKE",
target = "Lorg/apache/logging/log4j/Logger;warn(Ljava/lang/String;)V"
))
@@ -0,0 +1,38 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.block.BlockState;
import net.minecraft.block.ShapeContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.WallStandingBlockItem;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.WorldView;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(WallStandingBlockItem.class)
public class WallStandingBlockItem_creativeNoClipMixin
{
@Redirect(method = "getPlacementState", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/WorldView;canPlace(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/ShapeContext;)Z"
))
private boolean canCreativePlayerPlace(
WorldView worldView, BlockState state, BlockPos pos, ShapeContext context,
ItemPlacementContext itemcontext
)
{
PlayerEntity player = itemcontext.getPlayer();
if (CarpetSettings.creativeNoClip && player != null && player.isCreative() && player.abilities.flying)
{
// copy from canPlace
VoxelShape voxelShape = state.getCollisionShape(worldView, pos, context);
return voxelShape.isEmpty() || worldView.intersectsEntities(player, voxelShape.offset(pos.getX(), pos.getY(), pos.getZ()));
}
return worldView.canPlace(state, pos, context);
}
}
@@ -9,7 +9,9 @@ import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
@@ -24,6 +26,7 @@ import java.util.function.Consumer;
@Mixin(World.class)
public abstract class World_tickMixin implements WorldInterface
{
@Shadow @Final public boolean isClient;
CarpetProfiler.ProfilerToken currentSection;
CarpetProfiler.ProfilerToken entitySection;
@@ -79,7 +82,7 @@ public abstract class World_tickMixin implements WorldInterface
@Inject(method = "tickEntity", at = @At("HEAD"), cancellable = true)
private void startEntity(Consumer<Entity> consumer_1, Entity e, CallbackInfo ci)
{
if (!TickSpeed.process_entities && !(e instanceof PlayerEntity))
if (!(TickSpeed.process_entities || (e instanceof PlayerEntity) || (TickSpeed.is_superHot && isClient && e.getPrimaryPassenger() instanceof PlayerEntity)))
ci.cancel();
entitySection = CarpetProfiler.start_entity_section((World) (Object) this, e, CarpetProfiler.TYPE.ENTITY);
}
@@ -74,7 +74,7 @@ public class ClientNetworkHandler
TickSpeed.is_superHot = ((ByteTag) t).equals(ByteTag.ONE);
});
dataHandlers.put("TickPlayerActiveTimeout", (p, t) -> TickSpeed.player_active_timeout = ((AbstractNumberTag)t).getInt());
dataHandlers.put("scShape", (p, t) -> { // deprecated
dataHandlers.put("scShape", (p, t) -> { // deprecated // and unused // should remove for 1.17
if (CarpetClient.shapes != null)
CarpetClient.shapes.addShape((CompoundTag)t);
});
@@ -2,6 +2,8 @@ package carpet.patches;
import com.mojang.authlib.GameProfile;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket;
@@ -102,6 +104,7 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
public void kill(Text reason)
{
shakeOff();
this.server.send(new ServerTask(this.server.getTicks(), () -> {
this.networkHandler.onDisconnected(reason);
}));
@@ -120,9 +123,19 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
this.playerTick();
}
private void shakeOff()
{
if (getVehicle() instanceof PlayerEntity) stopRiding();
for (Entity passenger : getPassengersDeep())
{
if (passenger instanceof PlayerEntity) passenger.stopRiding();
}
}
@Override
public void onDeath(DamageSource cause)
{
shakeOff();
super.onDeath(cause);
setHealth(20);
this.hungerManager = new HungerManager();
@@ -5,6 +5,7 @@ import carpet.CarpetSettings;
import carpet.helpers.TickSpeed;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue;
import carpet.script.value.BooleanValue;
import carpet.script.value.EntityValue;
import carpet.script.value.FunctionValue;
import carpet.script.value.ListValue;
@@ -14,12 +15,16 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import carpet.settings.ParsedRule;
import carpet.utils.CarpetProfiler;
import carpet.utils.Messenger;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.TntEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.passive.MerchantEntity;
import net.minecraft.entity.projectile.ProjectileEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity;
@@ -41,6 +46,7 @@ import net.minecraft.village.Merchant;
import net.minecraft.village.TradeOffer;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.explosion.Explosion;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
@@ -51,6 +57,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -204,6 +211,7 @@ public class CarpetEventServer
{
if (callList.size() > 0)
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet events", CarpetProfiler.TYPE.GENERAL);
List<Value> argv = argumentSupplier.get(); // empty for onTickDone
ServerCommandSource source;
try
@@ -225,6 +233,7 @@ public class CarpetEventServer
if (!call.execute(source, argv)) fails.add(call);
}
for (Callback call : fails) callList.remove(call);
CarpetProfiler.end_current_section(currentSection);
}
}
@@ -862,6 +871,59 @@ public class CarpetEventServer
);
}
};
//copy of Explosion.getCausingEntity() #TRACK#
private static LivingEntity getExplosionCausingEntity(Entity entity)
{
if (entity == null) return null;
else if (entity instanceof TntEntity) return ((TntEntity)entity).getCausingEntity();
else if (entity instanceof LivingEntity) return (LivingEntity)entity;
else if (entity instanceof ProjectileEntity) {
Entity owner = ((ProjectileEntity)entity).getOwner();
if (owner instanceof LivingEntity) return (LivingEntity)owner;
}
return null;
}
public static final Event EXPLOSION_OUTCOME = new Event("explosion_outcome", 8, true)
{
@Override
public void onExplosion(ServerWorld world, Entity e, Supplier<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> affectedEntities, Explosion.DestructionType type)
{
handler.call(
() -> Arrays.asList(
ListValue.fromTriple(x, y, z),
NumericValue.of(power),
EntityValue.of(e),
EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
StringValue.of(type.name().toLowerCase(Locale.ROOT)),
BooleanValue.of(createFire),
new ListValue(affectedBlocks.stream().filter(b -> !world.isAir(b)). map( // da heck they send air blocks
b -> new BlockValue(world.getBlockState(b),world,b)
).collect(Collectors.toList())),
new ListValue(affectedEntities.stream().map(EntityValue::of).collect(Collectors.toList()))
), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
);
}
};
public static final Event EXPLOSION = new Event("explosion", 6, true)
{
@Override
public void onExplosion(ServerWorld world, Entity e, Supplier<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> affectedEntities, Explosion.DestructionType type)
{
handler.call(
() -> Arrays.asList(
ListValue.fromTriple(x, y, z),
NumericValue.of(power),
EntityValue.of(e),
EntityValue.of(attacker!= null?attacker.get():Event.getExplosionCausingEntity(e)),
StringValue.of(type.name().toLowerCase(Locale.ROOT)),
BooleanValue.of(createFire)
), () -> CarpetServer.minecraft_server.getCommandSource().withWorld(world)
);
}
};
public static String getEntityLoadEventName(EntityType<? extends Entity> et)
{
@@ -982,7 +1044,7 @@ public class CarpetEventServer
public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
public void onExplosion(ServerWorld world, Entity e, Supplier<LivingEntity> attacker, double x, double y, double z, float power, boolean createFire, List<BlockPos> affectedBlocks, List<Entity> affectedEntities, Explosion.DestructionType type) { }
public void onWorldEvent(ServerWorld world, BlockPos pos) { }
public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
public void onCarpetRuleChanges(ParsedRule<?> rule, ServerCommandSource source) { }
+144 -28
View File
@@ -3,6 +3,7 @@ package carpet.script;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.api.Auxiliary;
import carpet.script.argument.FileArgument;
import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.Module;
import carpet.script.command.CommandArgument;
@@ -17,9 +18,11 @@ import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.utils.CarpetProfiler;
import carpet.utils.Messenger;
import com.google.gson.JsonElement;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
@@ -32,6 +35,7 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import org.apache.commons.lang3.tuple.Pair;
@@ -43,11 +47,13 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.lang.Math.max;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
public class CarpetScriptHost extends ScriptHost
@@ -122,6 +128,7 @@ public class CarpetScriptHost extends ScriptHost
private static int execute(CommandContext<ServerCommandSource> ctx, String hostName, FunctionArgument<Value> funcSpec, List<String> paramNames) throws CommandSyntaxException
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
List<String> argNames = funcSpec.function.getArguments();
if ((argNames.size()-funcSpec.args.size()) != paramNames.size())
@@ -135,7 +142,9 @@ public class CarpetScriptHost extends ScriptHost
Value response = cHost.handleCommand(ctx.getSource(), funcSpec.function, args);
// will skip prints for new
//if (!response.isNull()) Messenger.m(ctx.getSource(), "gi " + response.getString());
return (int) response.readInteger();
int intres = (int) response.readInteger();
CarpetProfiler.end_current_section(currentSection);
return intres;
}
public LiteralArgumentBuilder<ServerCommandSource> addPathToCommand(
@@ -205,12 +214,15 @@ public class CarpetScriptHost extends ScriptHost
return s -> {
try
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
CarpetScriptHost cHost = null;
cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(s);
Value response = cHost.handleCommand(s, fun, Collections.singletonList(
(s.getEntity() instanceof ServerPlayerEntity)?new EntityValue(s.getEntity()):Value.NULL)
);
return response.getBoolean();
boolean res = response.getBoolean();
CarpetProfiler.end_current_section(currentSection);
return res;
}
catch (CommandSyntaxException e)
{
@@ -311,6 +323,107 @@ public class CarpetScriptHost extends ScriptHost
}
}
public Boolean addAppCommands(Consumer<Text> notifier)
{
if (appConfig.get(StringValue.of("commands")) != null)
{
try
{
LiteralArgumentBuilder<ServerCommandSource> command = readCommands(commandValidator);
if (command != null)
{
scriptServer.server.getCommandManager().getDispatcher().register(command);
return true;
}
else {
return false;
}
}
catch (CommandSyntaxException cse)
{
// failed
notifier.accept(Messenger.c("r Failed to build command system for "+getName()+" thus failed to load the app ", cse.getRawMessage()));
return null;
}
}
return addLegacyCommand(notifier);
}
private Boolean addLegacyCommand(Consumer<Text> notifier)
{
if (main == null) return false;
if (getFunction("__command") == null) return false;
if (scriptServer.isInvalidCommandRoot(getName()))
{
notifier.accept(Messenger.c("gi Tried to mask vanilla command."));
return null;
}
Function<ServerCommandSource, Boolean> configValidator;
try
{
configValidator = getCommandConfigPermissions();
}
catch (CommandSyntaxException e)
{
notifier.accept(Messenger.c("rb "+e.getMessage()));
return null;
}
String hostName = getName();
LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName).
requires((player) -> scriptServer.modules.containsKey(hostName) && commandValidator.apply(player) && configValidator.apply(player)).
executes( (c) ->
{
CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger();
});
for (String function : globaFunctionNames(main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()))
{
if (appConfig.getOrDefault(StringValue.of("legacy_command_type_support"), Value.FALSE).getBoolean())
{
try
{
FunctionValue functionValue = getFunction(function);
command = addPathToCommand(
command,
CommandToken.parseSpec(CommandToken.specFromSignature(functionValue), this),
FunctionArgument.fromCommandSpec(this, functionValue)
);
}
catch (CommandSyntaxException e)
{
return false;
}
}
else
{
command = command.
then(literal(function).
requires((player) -> scriptServer.modules.containsKey(hostName) && scriptServer.modules.get(hostName).getFunction(function) != null).
executes((c) -> {
CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString());
return (int) response.readInteger();
}).
then(argument("args...", StringArgumentType.greedyString()).
executes( (c) -> {
CarpetScriptHost targetHost = scriptServer.modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args..."));
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger();
})));
}
}
scriptServer.server.getCommandManager().getDispatcher().register(command);
return true;
}
public LiteralArgumentBuilder<ServerCommandSource> readCommands(Function<ServerCommandSource, Boolean> useValidator) throws CommandSyntaxException
{
Value commands = appConfig.get(StringValue.of("commands"));
@@ -440,7 +553,10 @@ public class CarpetScriptHost extends ScriptHost
{
try
{
return callLegacy(source, call, coords, arg);
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
Value res = callLegacy(source, call, coords, arg);
CarpetProfiler.end_current_section(currentSection);
return res;
}
catch (CarpetExpressionException exc)
{
@@ -680,31 +796,31 @@ public class CarpetScriptHost extends ScriptHost
private void dumpState()
{
Module.saveData(main, null, globalState, false);
Module.saveData(main, globalState);
}
private Tag loadState()
{
return Module.getData(main, null, false);
return Module.getData(main);
}
public Tag readFileTag(String file, boolean isShared)
public Tag readFileTag(FileArgument fdesc)
{
if (getName() == null && !isShared) return null;
if (file != null)
return Module.getData(main, file, isShared);
if (getName() == null && !fdesc.isShared) return null;
if (fdesc.resource != null)
return fdesc.getNbtData(main);
if (parent == null)
return globalState;
return ((CarpetScriptHost)parent).globalState;
}
public boolean writeTagFile(Tag tag, String file, boolean isShared)
public boolean writeTagFile(Tag tag, FileArgument fdesc)
{
if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
if (file!= null)
if (fdesc.resource != null)
{
return Module.saveData(main, file, tag, isShared);
return fdesc.saveNbtData(main, tag);
}
CarpetScriptHost responsibleHost = (parent != null)?(CarpetScriptHost) parent:this;
@@ -717,34 +833,34 @@ public class CarpetScriptHost extends ScriptHost
return true;
}
public boolean removeResourceFile(String resource, boolean isShared, String type)
public boolean removeResourceFile(FileArgument fdesc)
{
if (getName() == null && !isShared) return false; //
return Module.dropExistingFile(main, resource, type, isShared);
if (getName() == null && !fdesc.isShared) return false; //
return fdesc.dropExistingFile(main);
}
public boolean appendLogFile(String resource, boolean isShared, String type, List<String> data)
public boolean appendLogFile(FileArgument fdesc, List<String> data)
{
if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
return Module.appendToTextFile(main, resource, type, isShared, data);
if (getName() == null && !fdesc.isShared) return false; // if belongs to an app, cannot be default host.
return fdesc.appendToTextFile(main, data);
}
public List<String> readTextResource(String resource, String type, boolean isShared)
public List<String> readTextResource(FileArgument fdesc)
{
if (getName() == null && !isShared) return null;
return Module.listFile(main, resource, type, isShared);
if (getName() == null && !fdesc.isShared) return null;
return fdesc.listFile(main);
}
public JsonElement readJsonFile(String resource, String type, boolean isShared)
public JsonElement readJsonFile(FileArgument fdesc)
{
if (getName() == null && !isShared) return null;
return Module.readJsonFile(main, resource, type, isShared);
if (getName() == null && !fdesc.isShared) return null;
return fdesc.readJsonFile(main);
}
public Stream<String> listFolder(String resource, String type, boolean isShared)
public Stream<String> listFolder(FileArgument fdesc)
{
if (getName() == null && !isShared) return null; //
return Module.listFolder(main, resource, type, isShared);
if (getName() == null && !fdesc.isShared) return null; //
return fdesc.listFolder(main);
}
@@ -1,19 +1,15 @@
package carpet.script;
import carpet.CarpetSettings;
import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.BundledModule;
import carpet.CarpetServer;
import carpet.script.bundled.FileModule;
import carpet.script.bundled.Module;
import carpet.script.command.CommandToken;
import carpet.script.exception.InvalidCallbackException;
import carpet.script.value.FunctionValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.utils.CarpetProfiler;
import carpet.utils.Messenger;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode;
@@ -26,6 +22,7 @@ import net.minecraft.util.WorldSavePath;
import net.minecraft.util.math.BlockPos;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -93,6 +90,7 @@ public class CarpetScriptServer
registerBuiltInScript(BundledModule.carpetNative("chunk_display", false));
registerBuiltInScript(BundledModule.carpetNative("ai_tracker", false));
registerBuiltInScript(BundledModule.carpetNative("draw_beta", false));
registerBuiltInScript(BundledModule.carpetNative("shapes", true));
registerBuiltInScript(BundledModule.carpetNative("distance_beta", false));
}
@@ -136,7 +134,8 @@ public class CarpetScriptServer
{
try {
Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
Files.createDirectories(folder);
if (!Files.exists(folder))
Files.createDirectories(folder);
Optional<Path>
scriptPath = Files.list(folder)
.filter(script ->
@@ -149,7 +148,8 @@ public class CarpetScriptServer
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
{
Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
Files.createDirectories(globalFolder);
if (!Files.exists(globalFolder))
Files.createDirectories(globalFolder);
scriptPath = Files.walk(globalFolder)
.filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
@@ -194,7 +194,8 @@ public class CarpetScriptServer
}
try {
Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
Files.createDirectories(worldScripts);
if (!Files.exists(worldScripts))
Files.createDirectories(worldScripts);
Files.list(worldScripts)
.filter(f -> f.toString().endsWith(".sc"))
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
@@ -202,8 +203,9 @@ public class CarpetScriptServer
if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT))
{
Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
Files.createDirectories(globalScripts);
Files.walk(globalScripts)
if (!Files.exists(globalScripts))
Files.createDirectories(globalScripts);
Files.walk(globalScripts, FileVisitOption.FOLLOW_LINKS)
.filter(f -> f.toString().endsWith(".sc"))
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
}
@@ -223,6 +225,7 @@ public class CarpetScriptServer
public boolean addScriptHost(ServerCommandSource source, String name, Function<ServerCommandSource, Boolean> commandValidator,
boolean perPlayer, boolean autoload, boolean isRuleApp)
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet load", CarpetProfiler.TYPE.GENERAL);
if (commandValidator == null) commandValidator = p -> true;
long start = System.nanoTime();
name = name.toLowerCase(Locale.ROOT);
@@ -267,35 +270,23 @@ public class CarpetScriptServer
}
//addEvents(source, name);
String action = reload?"reloaded":"loaded";
if (newHost.appConfig.get(StringValue.of("commands")) != null)
{
try
{
LiteralArgumentBuilder<ServerCommandSource> command = newHost.readCommands(commandValidator);
if (command != null)
{
if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action+" with /"+name+" command");
server.getCommandManager().getDispatcher().register(command);
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
}
else {
if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action);
}
}
catch (CommandSyntaxException cse)
{
removeScriptHost(source, name, false, false);
Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app ", cse.getRawMessage());
return false;
}
}
else if (!addLegacyCommand(source, name, reload, !isRuleApp, commandValidator)) // this needs to be moved to config reader, only supporting legacy command here
Boolean isCommandAdded = newHost.addAppCommands(s -> {
if (!isRuleApp) Messenger.m(source, s);
});
if (isCommandAdded == null) // error should be dispatched
{
removeScriptHost(source, name, false, false);
Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app");
return false;
}
else if (isCommandAdded)
{
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action+" with /"+name+" command");
}
else
{
if (!isRuleApp) Messenger.m(source, "gi "+name+" app "+action);
}
if (newHost.isPerUser())
{
// that will provide player hosts right at the startup
@@ -307,94 +298,15 @@ public class CarpetScriptServer
FunctionValue onStart = newHost.getFunction("__on_start");
if (onStart != null) newHost.callNow(onStart, Collections.emptyList());
}
CarpetProfiler.end_current_section(currentSection);
long end = System.nanoTime();
CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms");
return true;
}
private boolean addLegacyCommand(ServerCommandSource source, String hostName, boolean isReload, boolean notifySource, Function<ServerCommandSource, Boolean> useValidator)
public boolean isInvalidCommandRoot(String appName)
{
CarpetScriptHost host = modules.get(hostName);
String loaded = isReload?"reloaded":"loaded";
if (host == null)
{
return true;
}
if (host.getFunction("__command") == null)
{
if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+".");
return true;
}
if (holyMoly.contains(hostName))
{
Messenger.m(source, "gi "+hostName+" app "+loaded+" with no command.");
Messenger.m(source, "gi Tried to mask vanilla command.");
return true;
}
Function<ServerCommandSource, Boolean> configValidator;
try
{
configValidator = host.getCommandConfigPermissions();
}
catch (CommandSyntaxException e)
{
Messenger.m(source, "rb "+e.getMessage());
return false;
}
LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName).
requires((player) -> modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)).
executes( (c) ->
{
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger();
});
for (String function : host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()))
{
if (host.appConfig.getOrDefault(StringValue.of("legacy_command_type_support"), Value.FALSE).getBoolean())
{
try
{
FunctionValue functionValue = host.getFunction(function);
command = host.addPathToCommand(
command,
CommandToken.parseSpec(CommandToken.specFromSignature(functionValue), host),
FunctionArgument.fromCommandSpec(host, functionValue)
);
}
catch (CommandSyntaxException e)
{
return false;
}
}
else
{
command = command.
then(literal(function).
requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null).
executes((c) -> {
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString());
return (int) response.readInteger();
}).
then(argument("args...", StringArgumentType.greedyString()).
executes( (c) -> {
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args..."));
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger();
})));
}
}
if (notifySource) Messenger.m(source, "gi "+hostName+" app "+loaded+" with /"+hostName+" command");
server.getCommandManager().getDispatcher().register(command);
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
return true;
return holyMoly.contains(appName);
}
@@ -498,11 +410,16 @@ public class CarpetScriptServer
public void tick()
{
CarpetProfiler.ProfilerToken token;
token = CarpetProfiler.start_section(null, "Scarpet schedule", CarpetProfiler.TYPE.GENERAL);
events.tick();
CarpetProfiler.end_current_section(token);
token = CarpetProfiler.start_section(null, "Scarpet app data", CarpetProfiler.TYPE.GENERAL);
for (CarpetScriptHost host : modules.values())
{
host.tick();
}
CarpetProfiler.end_current_section(token);
}
public void onClose()
@@ -553,4 +470,9 @@ public class CarpetScriptServer
init();
apps.forEach((s, data) -> addScriptHost(server.getCommandSource(), s,data.commandValidator, data.perUser,false, data.isRuleApp));
}
public void reAddCommands()
{
modules.values().forEach(host -> host.addAppCommands(s -> {}));
}
}
+9 -5
View File
@@ -19,7 +19,6 @@ import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ResolvedException;
import carpet.script.exception.ReturnStatement;
import carpet.script.exception.ThrowStatement;
import carpet.script.language.Arithmetic;
import carpet.script.language.ControlFlow;
import carpet.script.language.DataStructures;
@@ -232,7 +231,7 @@ public class Expression
if (exc instanceof ExitStatement)
return exc;
if (exc instanceof InternalExpressionException)
return new ExpressionException(c, e, token, exc.getMessage(), ((InternalExpressionException) exc).stack);
return ((InternalExpressionException) exc).promote(c, e, token);
if (exc instanceof ArithmeticException)
return new ExpressionException(c, e, token, "Your math is wrong, "+exc.getMessage());
if (exc instanceof ResolvedException)
@@ -314,6 +313,11 @@ public class Expression
addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble())));
}
public void addMathematicalUnaryIntFunction(String name, Function<Double, Long> fun)
{
addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble())));
}
public void addMathematicalBinaryFunction(String name, BiFunction<Double, Double, Double> fun)
{
addBinaryFunction(name, (w, v) ->
@@ -624,9 +628,9 @@ public class Expression
{
return exprProvider.get().evalValue(c, expectedType);
}
catch (ContinueStatement | BreakStatement | ReturnStatement | ThrowStatement exc)
catch (ContinueStatement | BreakStatement | ReturnStatement exc)
{
throw new ExpressionException(c, this, "Control flow functions, like continue, break, throw or return, should only be used in loops, try blocks, and functions respectively.");
throw new ExpressionException(c, this, "Control flow functions, like continue, break or return, should only be used in loops, and functions respectively.");
}
catch (ExitStatement exit)
{
@@ -761,7 +765,7 @@ public class Expression
{
if (token.surface.equals(";"))
{
throw new ExpressionException(c, this, token, "Unnecessary semicolon");
throw new ExpressionException(c, this, token, "Empty expression found for ';'");
}
throw new ExpressionException(c, this, token, "Missing parameter(s) for operator " + token);
}
+5 -1
View File
@@ -37,11 +37,15 @@ public abstract class ScriptHost
public Random getRandom(long aLong)
{
if (randomizers.size() > 1024)
if (randomizers.size() > 65536)
randomizers.clear();
return randomizers.computeIfAbsent(aLong, Random::new);
}
public boolean resetRandom(long aLong) {
return randomizers.remove(aLong) != null;
}
public static class ModuleData
{
Module parent;
@@ -406,6 +406,7 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
public int pos;
public int linepos;
public int lineno;
public static final Token NONE = new Token();
public Token morphedInto(TokenType newType, String newSurface)
{
Token created = new Token();
+253 -69
View File
@@ -3,11 +3,13 @@ package carpet.script.api;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.fakes.MinecraftServerInterface;
import carpet.fakes.ServerWorldInterface;
import carpet.fakes.StatTypeInterface;
import carpet.fakes.ThreadedAnvilChunkStorageInterface;
import carpet.helpers.FeatureGenerator;
import carpet.mixins.PlayerListHeaderS2CPacketMixin;
import carpet.script.bundled.Module;
import carpet.script.argument.FileArgument;
import carpet.script.CarpetContext;
import carpet.script.CarpetEventServer;
import carpet.script.CarpetScriptHost;
@@ -20,7 +22,10 @@ import carpet.script.argument.FunctionArgument;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.utils.FixedCommandSource;
import carpet.script.utils.InputValidator;
import carpet.script.utils.ScarpetJsonDeserializer;
import carpet.script.utils.ShapeDispatcher;
import carpet.script.utils.WorldTools;
@@ -36,6 +41,10 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import carpet.utils.Messenger;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import net.minecraft.SharedConstants;
import net.minecraft.block.BlockState;
import net.minecraft.command.DataCommandStorage;
import net.minecraft.entity.Entity;
@@ -46,6 +55,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket;
@@ -53,6 +63,11 @@ import net.minecraft.network.packet.s2c.play.PlayerListHeaderS2CPacket;
import net.minecraft.network.packet.s2c.play.TitleS2CPacket;
import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.resource.DataPackSettings;
import net.minecraft.resource.ReloadableResourceManagerImpl;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ServerResourceManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
@@ -64,21 +79,42 @@ import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.crash.CrashException;
import net.minecraft.util.Util;
import net.minecraft.util.WorldSavePath;
import net.minecraft.util.dynamic.RegistryOps;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.SaveProperties;
import net.minecraft.world.World;
import net.minecraft.world.biome.source.BiomeAccess;
import net.minecraft.world.border.WorldBorderListener;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.gen.GeneratorOptions;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.level.UnmodifiableLevelProperties;
import net.minecraft.world.level.storage.LevelStorage;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -97,8 +133,9 @@ import static java.lang.Math.min;
public class Auxiliary {
public static final String MARKER_STRING = "__scarpet_marker";
private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
public static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
@Deprecated
public static String recognizeResource(Value value, boolean isFloder)
{
String origfile = value.getString();
@@ -111,28 +148,20 @@ public class Auxiliary {
return file;
}
public static Triple<String, String, Boolean> getFileDescriptor(List<LazyValue> lv, Context c, boolean isFloder)
{
if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
String resource = recognizeResource(lv.get(0).evalValue(c), isFloder);
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String type = shared ? origtype.substring(7) : origtype; //len(shared_)
if (!Module.supportedTypes.containsKey(type))
throw new InternalExpressionException("Unsupported file type: "+origtype);
if (type.equals("folder") && !isFloder)
throw new InternalExpressionException("Folder types are no supported for this IO function");
return Triple.of(resource, type, shared);
}
public static void apply(Expression expression)
{
expression.addLazyFunction("sound", -1, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString());
if (lv.size() == 0)
{
Value ret = ListValue.wrap(Registry.SOUND_EVENT.getIds().stream().map(ValueConversions::of));
return (_c, _t) -> ret;
}
String rawString = lv.get(0).evalValue(c).getString();
Identifier soundName = new Identifier(rawString);
Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
if (Registry.SOUND_EVENT.get(soundName) == null)
throw new InternalExpressionException("No such sound: "+soundName.getPath());
throw new ThrowStatement(rawString, Throwables.UNKNOWN_SOUND);
float volume = 1.0F;
float pitch = 1.0F;
SoundCategory mixer = SoundCategory.MASTER;
@@ -165,6 +194,12 @@ public class Auxiliary {
expression.addLazyFunction("particle", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
if (lv.size() == 0)
{
Value ret = ListValue.wrap(Registry.PARTICLE_TYPE.getIds().stream().map(ValueConversions::of));
return (_c, _t) -> ret;
}
MinecraftServer ms = cc.s.getMinecraftServer();
ServerWorld world = cc.s.getWorld();
Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
@@ -847,39 +882,28 @@ public class Auxiliary {
expression.addLazyFunction("list_files", 2, (c, t, lv) ->
{
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
FileArgument fdesc = FileArgument.from(lv, c, true, FileArgument.Reason.READ);
Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc);
if (files == null) return LazyValue.NULL;
Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
return (cc, tt) -> ret;
});
expression.addLazyFunction("read_file", 2, (c, t, lv) ->
{
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.READ);
Value retVal;
if (fdesc.getMiddle().equals("nbt"))
if (fdesc.type == FileArgument.Type.NBT)
{
Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state);
}
else if (fdesc.getMiddle().equals("json"))
else if (fdesc.type == FileArgument.Type.JSON)
{
JsonElement json;
try
{
json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
}
catch (JsonParseException e)
{
Throwable exception = e;
if(e.getCause() != null)
exception = e.getCause();
throw new InternalExpressionException("Failed to read JSON file: "+exception.getMessage());
}
Value parsedJson = gson.fromJson(json, Value.class);
json = ((CarpetScriptHost) c.host).readJsonFile(fdesc);
Value parsedJson = GSON.fromJson(json, Value.class);
if (parsedJson == null)
retVal = Value.NULL;
else
@@ -887,7 +911,7 @@ public class Auxiliary {
}
else
{
List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc);
if (content == null) return LazyValue.NULL;
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
}
@@ -895,32 +919,29 @@ public class Auxiliary {
});
expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
? LazyValue.TRUE
: LazyValue.FALSE;
FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.DELETE);
return ((CarpetScriptHost) c.host).removeResourceFile(fdesc) ? LazyValue.TRUE : LazyValue.FALSE;
});
expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
FileArgument fdesc = FileArgument.from(lv, c, false, FileArgument.Reason.CREATE);
boolean success;
if (fdesc.getMiddle().equals("nbt"))
if (fdesc.type == FileArgument.Type.NBT)
{
Value val = lv.get(2).evalValue(c);
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
}
else if (fdesc.getMiddle().equals("json"))
else if (fdesc.type == FileArgument.Type.JSON)
{
List<String> data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson()));
((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle());
success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
List<String> data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson()));
((CarpetScriptHost) c.host).removeResourceFile(fdesc);
success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
}
else
{
@@ -945,27 +966,22 @@ public class Auxiliary {
data.add(lv.get(i).evalValue(c).getString());
}
}
success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
success = ((CarpetScriptHost) c.host).appendLogFile(fdesc, data);
}
return success?LazyValue.TRUE:LazyValue.FALSE;
});
//write_file
expression.addLazyFunction("load_app_data", -1, (c, t, lv) ->
{
String file = null;
boolean shared = false;
FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.READ);
if (lv.size()>0)
{
c.host.issueDeprecation("load_app_data(...) with arguments");
file = recognizeResource(lv.get(0).evalValue(c), false);
if (lv.size() > 1)
{
shared = lv.get(1).evalValue(c).getBoolean();
}
String resource = recognizeResource(lv.get(0).evalValue(c), false);
boolean shared = lv.size() > 1 && lv.get(1).evalValue(c).getBoolean();
fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.READ);
}
Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc);
if (state == null)
return (cc, tt) -> Value.NULL;
Value retVal = new NBTSerializableValue(state);
@@ -977,22 +993,19 @@ public class Auxiliary {
if (lv.size() == 0)
throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file");
Value val = lv.get(0).evalValue(c);
String file = null;
boolean shared = false;
FileArgument fdesc = new FileArgument(null, FileArgument.Type.NBT, null, false, false, FileArgument.Reason.CREATE);
if (lv.size()>1)
{
c.host.issueDeprecation("store_app_data(...) with more than one argument");
file = recognizeResource(lv.get(1).evalValue(c), false);
if (lv.size() > 2)
{
shared = lv.get(2).evalValue(c).getBoolean();
}
String resource = recognizeResource(lv.get(1).evalValue(c), false);
boolean shared = lv.size() > 2 && lv.get(2).evalValue(c).getBoolean();
fdesc = new FileArgument(resource, FileArgument.Type.NBT, null, false, shared, FileArgument.Reason.CREATE);
}
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared);
boolean success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc);
return success?LazyValue.TRUE:LazyValue.FALSE;
});
@@ -1089,8 +1102,179 @@ public class Auxiliary {
if (old_nbt == null) return LazyValue.NULL;
return (_c, _t) -> new NBTSerializableValue(old_nbt);
});
// script run create_datapack('foo', {'foo' -> {'bar.json' -> {'c' -> true,'d' -> false,'e' -> {'foo' -> [1,2,3]},'a' -> 'foobar','b' -> 5}}})
expression.addLazyFunction("create_datapack", 2, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
String origName = lv.get(0).evalValue(c).getString();
String name = InputValidator.validateSimpleString(origName, true);
MinecraftServer server = cc.s.getMinecraftServer();
for (String dpName : server.getDataPackManager().getNames())
{
if (dpName.equalsIgnoreCase("file/"+name+".zip") ||
dpName.equalsIgnoreCase("file/"+name))
return LazyValue.NULL;
}
Value dpdata = lv.get(1).evalValue(c);
if (!(dpdata instanceof MapValue))
throw new InternalExpressionException("datapack data needs to be a valid map type");
ResourcePackManager packManager = server.getDataPackManager();
Path dbFloder = server.getSavePath(WorldSavePath.DATAPACKS);
Path packFloder = dbFloder.resolve(name+".zip");
if (Files.exists(packFloder) || Files.exists(dbFloder.resolve(name))) return LazyValue.NULL;
Boolean [] successful = new Boolean[]{true};
server.submitAndJoin( () ->
{
try {
//Files.createDirectory(packFloder);
try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + packFloder.toUri().toString()), ImmutableMap.of("create", "true"))) {
Path zipRoot = zipfs.getPath("/");
zipValueToJson(zipRoot.resolve("pack.mcmeta"), MapValue.wrap(
ImmutableMap.of(StringValue.of("pack"), MapValue.wrap(ImmutableMap.of(
StringValue.of("pack_format"), new NumericValue(SharedConstants.getGameVersion().getPackVersion()),
StringValue.of("description"), StringValue.of(name),
StringValue.of("source"), StringValue.of("scarpet")
)))
));
walkTheDPMap((MapValue) dpdata, zipRoot);
}
packManager.scanPacks();
ResourcePackProfile resourcePackProfile = packManager.getProfile("file/" + name + ".zip");
if (resourcePackProfile == null || packManager.getEnabledProfiles().contains(resourcePackProfile)) {
throw new IOException();
}
List<ResourcePackProfile> list = Lists.newArrayList(packManager.getEnabledProfiles());
resourcePackProfile.getInitialPosition().insert(list, resourcePackProfile, p -> p, false);
server.reloadResources(list.stream().map(ResourcePackProfile::getName).collect(Collectors.toList())).
exceptionally(exc -> {
successful[0] = false;
return null;
}).join();
if (!successful[0]) {
throw new IOException();
}
} catch (IOException e)
{
successful[0] = false;
try {
FileUtils.forceDelete(packFloder.toFile());
} catch (IOException ignored) {
throw new InternalExpressionException("Failed to install a datapack and failed to clean up after it");
}
}
});
return successful[0]?LazyValue.TRUE:LazyValue.FALSE;
});
expression.addLazyFunction("enable_hidden_dimensions", 0, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
// from minecraft.server.Main.main
MinecraftServer server = cc.s.getMinecraftServer();
LevelStorage.Session session = ((MinecraftServerInterface)server).getCMSession();
DataPackSettings dataPackSettings = session.getDataPackSettings();
ResourcePackManager resourcePackManager = server.getDataPackManager();
DataPackSettings dataPackSettings2 = MinecraftServer.loadDataPacks(resourcePackManager, dataPackSettings == null ? DataPackSettings.SAFE_MODE : dataPackSettings, false);
ServerResourceManager serverRM = ((MinecraftServerInterface)server).getResourceManager();
ReloadableResourceManagerImpl resourceManager = (ReloadableResourceManagerImpl) serverRM.getResourceManager();
//believe the other one will fillup based on the datapacks only.
resourceManager.close();
resourcePackManager.createResourcePacks().forEach(resourceManager::addPack);
//not sure its needed, but doesn't seem to have a negative effect and might be used in some custom shtuff
serverRM.loadRegistryTags();
RegistryOps<Tag> registryOps = RegistryOps.of(NbtOps.INSTANCE, serverRM.getResourceManager(), (DynamicRegistryManager.Impl) server.getRegistryManager());
SaveProperties saveProperties = session.readLevelProperties(registryOps, dataPackSettings2);
if (saveProperties == null) return LazyValue.NULL;
//session.backupLevelDataFile(server.getRegistryManager(), saveProperties); // no need
// MinecraftServer.createWorlds
// save properties should now contain dimension settings
GeneratorOptions generatorOptions = saveProperties.getGeneratorOptions();
boolean bl = generatorOptions.isDebugWorld();
long l = generatorOptions.getSeed();
long m = BiomeAccess.hashSeed(l);
Map<RegistryKey<World>, ServerWorld> existing_worlds = ((MinecraftServerInterface)server).getCMWorlds();
List<Value> addeds = new ArrayList<>();
for (Map.Entry<RegistryKey<DimensionOptions>, DimensionOptions> entry : generatorOptions.getDimensions().getEntries()) {
RegistryKey<DimensionOptions> registryKey = entry.getKey();
if (!existing_worlds.containsKey(registryKey))
{
addeds.add(ValueConversions.of(registryKey.getValue()));
RegistryKey<World> registryKey2 = RegistryKey.of(Registry.DIMENSION, registryKey.getValue());
DimensionType dimensionType3 = entry.getValue().getDimensionType();
ChunkGenerator chunkGenerator3 = entry.getValue().getChunkGenerator();
UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, ((ServerWorldInterface) server.getOverworld()).getWorldPropertiesCM());
ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false);
server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder()));
existing_worlds.put(registryKey2, serverWorld2);
}
}
Value ret = ListValue.wrap(addeds);
return (_c, _t) -> ret;
});
}
private static void zipValueToJson(Path path, Value output) throws IOException
{
JsonElement element = output.toJson();
if (element == null)
throw new InternalExpressionException("Cannot interpret "+output.getPrettyString()+" as a json object");
String string = GSON.toJson(element);
Files.createDirectories(path.getParent());
BufferedWriter bufferedWriter = Files.newBufferedWriter(path);
Throwable incident = null;
try
{
bufferedWriter.write(string);
}
catch (Throwable shitHappened)
{
incident = shitHappened;
throw shitHappened;
}
finally
{
if (incident != null) {
try {
bufferedWriter.close();
} catch (Throwable otherShitHappened) {
incident.addSuppressed(otherShitHappened);
}
} else {
bufferedWriter.close();
}
}
}
private static void walkTheDPMap(MapValue node, Path path) throws IOException
{
Map<Value,Value> items = node.getMap();
for (Map.Entry<Value, Value> entry : items.entrySet())
{
Value val = entry.getValue();
String strkey = entry.getKey().getString();
Path child = path.resolve(strkey);
if (strkey.endsWith(".json"))
{
zipValueToJson(child, val);
}
else
{
if (!(val instanceof MapValue)) throw new InternalExpressionException("Value of "+strkey+" should be a map");
Files.createDirectory(child);
walkTheDPMap((MapValue) val, child);
}
}
}
private static <T> Stat<T> getStat(StatType<T> type, Identifier id)
{
T key = type.getRegistry().get(id);
@@ -6,6 +6,8 @@ import carpet.script.CarpetContext;
import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.value.ListValue;
import carpet.script.value.NBTSerializableValue;
import carpet.script.value.NullValue;
@@ -91,7 +93,7 @@ public class Inventories {
Item item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()).getItem();
if (lv.size() == 1)
{
Value ret = ListValue.wrap(tagManager.getItems().getTagsFor(item).stream().map(ValueConversions::of).collect(Collectors.toList()));
Value ret = ListValue.wrap(tagManager.getItems().getTags().entrySet().stream().filter(e -> e.getValue().contains(item)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList()));
return (_c, _t) -> ret;
}
String tag = lv.get(1).evalValue(c).getString();
@@ -119,14 +121,7 @@ public class Inventories {
}
}
List<Recipe<?>> recipes;
try
{
recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
}
catch (InvalidIdentifierException ignored)
{
return LazyValue.NULL;
}
recipes = ((RecipeManagerInterface) cc.s.getMinecraftServer().getRecipeManager()).getAllMatching(type, new Identifier(recipeName));
if (recipes.isEmpty())
return LazyValue.NULL;
List<Value> recipesOutput = new ArrayList<>();
@@ -197,17 +192,9 @@ public class Inventories {
{
String itemStr = lv.get(0).evalValue(c).getString();
Item item;
try
{
Identifier id = new Identifier(itemStr);
item = Registry.ITEM.get(id);
if (item == Items.AIR && !id.getPath().equalsIgnoreCase("air"))
throw new InvalidIdentifierException("boo");
}
catch (InvalidIdentifierException ignored)
{
throw new InternalExpressionException("Incorrect item: "+itemStr);
}
Identifier id = new Identifier(itemStr);
item = Registry.ITEM.getOrEmpty(id).orElseThrow(() -> new ThrowStatement(itemStr, Throwables.UNKNOWN_ITEM));
if (!item.hasRecipeRemainder()) return LazyValue.NULL;
Value ret = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.ITEM.getId(item.getRecipeRemainder())));
return (_c, _t ) -> ret;
@@ -6,6 +6,8 @@ import carpet.script.CarpetContext;
import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.value.EntityValue;
import carpet.script.value.ListValue;
import carpet.script.value.NullValue;
@@ -33,6 +35,8 @@ import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Scoreboards {
@@ -77,16 +81,27 @@ public class Scoreboards {
Value ret = ListValue.wrap(scoreboard.getAllPlayerScores(objective).stream().map(s -> new StringValue(s.getPlayerName())).collect(Collectors.toList()));
return (_c, _t) -> ret;
}
String key = getScoreboardKeyFromValue(lv.get(1).evalValue(c));
if (!scoreboard.playerHasObjective(key, objective) && lv.size()==2)
if(lv.size()==2) {
if(scoreboard.playerHasObjective(key, objective)) {
return (_c,_t) -> NumericValue.of(scoreboard.getPlayerScore(key,objective).getScore());
}
return LazyValue.NULL;
ScoreboardPlayerScore scoreboardPlayerScore = scoreboard.getPlayerScore(key, objective);
Value retval = new NumericValue(scoreboardPlayerScore.getScore());
if (lv.size() > 2)
{
scoreboardPlayerScore.setScore(NumericValue.asNumber(lv.get(2).evalValue(c)).getInt());
}
return (_c, _t) -> retval;
Value value = lv.get(2).evalValue(c);
if(value.isNull()) {
ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective);
scoreboard.resetPlayerScore(key,objective);
return (_c,_t) -> NumericValue.of(score.getScore());
}
if (value instanceof NumericValue) {
ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective);
score.setScore(NumericValue.asNumber(value).getInt());
return (_c,_t) -> NumericValue.of(score.getScore());
}
throw new InternalExpressionException("'scoreboard' requires a number or null as the third parameter");
});
expression.addLazyFunction("scoreboard_remove", -1, (c, t, lv)->
@@ -131,12 +146,13 @@ public class Scoreboards {
criterion = ScoreboardCriterion.createStatCriterion(critetionName).orElse(null);
if (criterion==null)
{
throw new InternalExpressionException("Unknown scoreboard criterion: "+critetionName);
throw new ThrowStatement(critetionName, Throwables.UNKNOWN_CRITERION);
}
}
ScoreboardObjective objective = scoreboard.getObjective(objectiveName);
if (objective != null) {
c.host.issueDeprecation("reading or modifying an objective's criterion with scoreboard_add");
if(lv.size() == 1) return (_c, _t) -> StringValue.of(objective.getCriterion().getName());
if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.NULL;
((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective);
@@ -150,6 +166,72 @@ public class Scoreboards {
return LazyValue.TRUE;
});
expression.addLazyFunction("scoreboard_property", -1, (c, t, lv) ->
{
if(lv.size() < 2) throw new InternalExpressionException("'scoreboard_property' requires at least two parameters");
CarpetContext cc = (CarpetContext)c;
Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard();
ScoreboardObjective objective = scoreboard.getObjective(lv.get(0).evalValue(c).getString());
if(objective == null) return LazyValue.NULL;
boolean modify = lv.size() > 2;
Value setValue = null;
if(modify) {
setValue = lv.get(2).evalValue(c);
}
String property = lv.get(1).evalValue(c).getString();
switch (property) {
case "criterion":
if(modify) {
ScoreboardCriterion criterion = ScoreboardCriterion.createStatCriterion(setValue.getString()).orElse(null);
if (criterion==null) throw new InternalExpressionException("Unknown scoreboard criterion: "+ setValue.getString());
if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.FALSE;
((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective);
((ScoreboardObjective_scarpetMixin) objective).setCriterion(criterion);
(((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().computeIfAbsent(criterion, (criterion1) -> Lists.newArrayList())).add(objective);
scoreboard.updateObjective(objective);
return LazyValue.TRUE;
}
return (_c, _t) -> StringValue.of(objective.getCriterion().getName());
case "display_name":
if(modify) {
Text text = (setValue instanceof FormattedTextValue)?((FormattedTextValue) setValue).getText():new LiteralText(setValue.getString());
objective.setDisplayName(text);
return LazyValue.TRUE;
}
return (_c, _t) -> new FormattedTextValue(objective.getDisplayName());
case "display_slot":
if(modify) {
int slotId = Scoreboard.getDisplaySlotId(setValue.getString());
if(slotId == -1) throw new InternalExpressionException("Unknown scoreboard display slot: " + setValue.getString());
if(objective.equals(scoreboard.getObjectiveForSlot(slotId))) {
return LazyValue.FALSE;
}
scoreboard.setObjectiveSlot(slotId,objective);
return LazyValue.TRUE;
}
List<Value> slots = new ArrayList<>();
for(int i = 0; i < 19; i++) {
if (scoreboard.getObjectiveForSlot(i) == objective) {
String slotName = Scoreboard.getDisplaySlotName(i);
slots.add(StringValue.of(slotName));
}
}
return (_c, _t) -> ListValue.wrap(slots);
case "render_type":
if(modify) {
ScoreboardCriterion.RenderType renderType = ScoreboardCriterion.RenderType.getType(setValue.getString().toLowerCase());
if(objective.getRenderType().equals(renderType)) return LazyValue.FALSE;
objective.setRenderType(renderType);
return LazyValue.TRUE;
}
return (_c, _t) -> StringValue.of(objective.getRenderType().getName());
default:
throw new InternalExpressionException("scoreboard property '" + property + "' is not a valid property");
}
});
expression.addLazyFunction("scoreboard_display", 2, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
@@ -408,7 +490,7 @@ public class Scoreboards {
team.setSuffix(suffix);
break;
default:
throw new InternalExpressionException("team property " + propertyVal.getString() + " is not a valid property");
throw new InternalExpressionException("team property '" + propertyVal.getString() + "' is not a valid property");
}
return LazyValue.TRUE;
});
+106 -20
View File
@@ -19,6 +19,8 @@ import carpet.script.LazyValue;
import carpet.script.argument.BlockArgument;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.utils.BiomeInfo;
import carpet.script.utils.WorldTools;
import carpet.script.value.BlockValue;
@@ -48,6 +50,7 @@ import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
@@ -59,7 +62,7 @@ import net.minecraft.item.SwordItem;
import net.minecraft.item.TridentItem;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.network.packet.s2c.play.ExplosionS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketType;
@@ -78,6 +81,7 @@ import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.Heightmap;
import net.minecraft.world.LightType;
@@ -87,6 +91,7 @@ import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.gen.ChunkRandom;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
@@ -95,6 +100,7 @@ import net.minecraft.world.level.ServerWorldProperties;
import net.minecraft.world.poi.PointOfInterest;
import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestType;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -106,7 +112,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -121,8 +126,7 @@ public class WorldAccess {
put("unknown", ChunkTicketType.UNKNOWN); // unknown
}};
// dummy entity for dummy requirements in the loot tables (see snowball)
private static FallingBlockEntity DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null);
private static FallingBlockEntity DUMMY_ENTITY = null;
private static LazyValue booleanStateTest(
Context c,
String name,
@@ -336,8 +340,8 @@ public class WorldAccess {
String poiType = lv.get(locator.offset+1).evalValue(c).getString().toLowerCase(Locale.ROOT);
if (!"any".equals(poiType))
{
PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiType));
if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiType)) return LazyValue.NULL;
PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiType))
.orElseThrow(() -> new ThrowStatement(poiType, Throwables.UNKNOWN_POI));
condition = (tt) -> tt == type;
}
if (locator.offset + 2 < lv.size())
@@ -391,9 +395,8 @@ public class WorldAccess {
return LazyValue.FALSE;
}
String poiTypeString = poi.getString().toLowerCase(Locale.ROOT);
PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.get(new Identifier(poiTypeString));
// solving lack of null with defaulted registries
if (type == PointOfInterestType.UNEMPLOYED && !"unemployed".equals(poiTypeString)) throw new InternalExpressionException("Unknown POI type: "+poiTypeString);
PointOfInterestType type = Registry.POINT_OF_INTEREST_TYPE.getOrEmpty(new Identifier(poiTypeString))
.orElseThrow(() -> new ThrowStatement(poiTypeString, Throwables.UNKNOWN_POI));
int occupancy = 0;
if (locator.offset + 1 < lv.size())
{
@@ -872,9 +875,8 @@ public class WorldAccess {
{
playerBreak = true;
String itemString = val.getString();
item = Registry.ITEM.get(new Identifier(itemString));
if (item == Items.AIR && !itemString.equals("air"))
throw new InternalExpressionException("Incorrect item: " + itemString);
item = Registry.ITEM.getOrEmpty(new Identifier(itemString))
.orElseThrow(() -> new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM));
}
}
CompoundTag tag = null;
@@ -932,6 +934,7 @@ public class WorldAccess {
{
if (how > 0)
tool.addEnchantment(Enchantments.FORTUNE, (int) how);
if (DUMMY_ENTITY == null) DUMMY_ENTITY = new FallingBlockEntity(EntityType.FALLING_BLOCK, null);
Block.dropStacks(state, world, where, be, DUMMY_ENTITY, tool);
}
}
@@ -972,6 +975,88 @@ public class WorldAccess {
return success ? LazyValue.TRUE : LazyValue.FALSE;
});
expression.addLazyFunction("create_explosion", -1, (c, t, lv) ->
{
if (lv.isEmpty())
throw new InternalExpressionException("'create_explosion' requires at least a position to explode");
CarpetContext cc = (CarpetContext)c;
float powah = 4.0f;
Explosion.DestructionType mode = Explosion.DestructionType.BREAK;
boolean createFire = false;
Entity source = null;
LivingEntity attacker = null;
Vector3Argument location = Vector3Argument.findIn(cc, lv, 0, false, true);
Vec3d pos = location.vec;
if (lv.size() > location.offset)
{
powah = NumericValue.asNumber(lv.get(location.offset).evalValue(c), "explosion power").getFloat();
if (powah < 0) throw new InternalExpressionException("Explosion power cannot be negative");
if (lv.size() > location.offset+1)
{
String strval = lv.get(location.offset+1).evalValue(c).getString();
try {
mode = Explosion.DestructionType.valueOf(strval.toUpperCase(Locale.ROOT));
}
catch (IllegalArgumentException ile) { throw new InternalExpressionException("Illegal explosions block behaviour: "+strval); }
if (lv.size() > location.offset+2)
{
createFire = lv.get(location.offset+2).evalValue(c, Context.BOOLEAN).getBoolean();
if (lv.size() > location.offset+3)
{
Value enVal= lv.get(location.offset+3).evalValue(c);
if (enVal.isNull()) {} // is null already
else if (enVal instanceof EntityValue)
{
source = ((EntityValue) enVal).getEntity();
}
else
{
throw new InternalExpressionException("Fourth parameter of the explosion has to be an entity, not "+enVal.getTypeString());
}
if (lv.size() > location.offset+4)
{
enVal = lv.get(location.offset+4).evalValue(c);
if (enVal.isNull()) {} // is null already
else if (enVal instanceof EntityValue)
{
Entity attackingEntity = ((EntityValue) enVal).getEntity();
if (attackingEntity instanceof LivingEntity)
{
attacker = (LivingEntity) attackingEntity;
}
else throw new InternalExpressionException("Attacking entity needs to be a living thing, "+
ValueConversions.of(Registry.ENTITY_TYPE.getId(attackingEntity.getType())).getString() +" ain't it.");
}
else
{
throw new InternalExpressionException("Fifth parameter of the explosion has to be a living entity, not "+enVal.getTypeString());
}
}
}
}
}
}
LivingEntity theAttacker = attacker;
float thePowah = powah;
// copy of ServerWorld.createExplosion #TRACK#
Explosion explosion = new Explosion(cc.s.getWorld(), source, null, null, pos.x, pos.y, pos.z, powah, createFire, mode){
@Override
public @Nullable LivingEntity getCausingEntity() {
return theAttacker;
}
};
explosion.collectBlocksAndDamageEntities();
explosion.affectWorld(false);
if (mode == Explosion.DestructionType.NONE) explosion.clearAffectedBlocks();
cc.s.getWorld().getPlayers().forEach(spe -> {
if (spe.squaredDistanceTo(pos) < 4096.0D)
spe.networkHandler.sendPacket(new ExplosionS2CPacket(pos.x, pos.y, pos.z, thePowah, explosion.getAffectedBlocks(), explosion.getAffectedPlayers().get(spe)));
});
return LazyValue.TRUE;
});
// TODO rename to use_item
expression.addLazyFunction("place_item", -1, (c, t, lv) ->
{
@@ -1127,7 +1212,8 @@ public class WorldAccess {
BlockArgument blockLocator = BlockArgument.findIn(cc, lv, 0, true);
if (blockLocator.offset == lv.size())
{
Value ret = ListValue.wrap(tagManager.getBlocks().getTagsFor(blockLocator.block.getBlockState().getBlock()).stream().map(ValueConversions::of).collect(Collectors.toList()));
Block target = blockLocator.block.getBlockState().getBlock();
Value ret = ListValue.wrap(tagManager.getBlocks().getTags().entrySet().stream().filter(e -> e.getValue().contains(target)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList()));
return (_c, _t) -> ret;
}
String tag = lv.get(blockLocator.offset).evalValue(c).getString();
@@ -1151,7 +1237,7 @@ public class WorldAccess {
if (locator.replacement != null)
{
biome = world.getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(locator.replacement));
if (biome == null) return LazyValue.NULL;
if (biome == null) throw new ThrowStatement(locator.replacement, Throwables.UNKNOWN_BIOME) ;
}
else
{
@@ -1180,9 +1266,8 @@ public class WorldAccess {
throw new InternalExpressionException("'set_biome' needs a biome name as an argument");
String biomeName = lv.get(locator.offset+0).evalValue(c).getString();
// from locatebiome command code
Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).get(new Identifier(biomeName));
if (biome == null)
throw new InternalExpressionException("Unknown biome: "+biomeName);
Biome biome = cc.s.getMinecraftServer().getRegistryManager().get(Registry.BIOME_KEY).getOrEmpty(new Identifier(biomeName))
.orElseThrow(() -> new ThrowStatement(biomeName, Throwables.UNKNOWN_BIOME));
boolean doImmediateUpdate = true;
if (lv.size() > locator.offset+1)
{
@@ -1251,8 +1336,8 @@ public class WorldAccess {
if (!(requested instanceof NullValue))
{
String reqString = requested.getString();
structure = Registry.STRUCTURE_FEATURE.get(new Identifier(reqString));
if (structure == null) throw new InternalExpressionException("Unknown structure: " + reqString);
structure = Registry.STRUCTURE_FEATURE.getOrEmpty(new Identifier(reqString))
.orElseThrow(() -> new ThrowStatement(reqString, Throwables.UNKNOWN_STRUCTURE));
}
if (lv.size() > locator.offset+1)
{
@@ -1332,7 +1417,7 @@ public class WorldAccess {
throw new InternalExpressionException("'set_structure requires at least position and a structure name");
String structureName = lv.get(locator.offset).evalValue(c).getString().toLowerCase(Locale.ROOT);
ConfiguredStructureFeature<?, ?> configuredStructure = FeatureGenerator.resolveConfiguredStructure(structureName, world, pos);
if (configuredStructure == null) throw new InternalExpressionException("Unknown structure: "+structureName);
if (configuredStructure == null) throw new ThrowStatement(structureName, Throwables.UNKNOWN_STRUCTURE);
// good 'ol pointer
Value[] result = new Value[]{Value.NULL};
// technically a world modification. Even if we could let it slide, we will still park it
@@ -1382,6 +1467,7 @@ public class WorldAccess {
{
if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument");
CarpetContext cc = (CarpetContext)c;
cc.host.issueDeprecation("custom_dimension()");
String worldKey = lv.get(0).evalValue(c).getString();
Long seed = null;
@@ -0,0 +1,502 @@
package carpet.script.argument;
import carpet.CarpetServer;
import carpet.script.Context;
import carpet.script.LazyValue;
import carpet.script.bundled.Module;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.value.MapValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.PositionTracker;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagReaders;
import net.minecraft.util.WorldSavePath;
import net.minecraft.util.crash.CrashException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileArgument
{
public String resource;
public Type type;
public String zipContainer;
public boolean isFolder;
public boolean isShared;
public Reason reason;
private FileSystem zfs;
private Path zipPath;
public final static Object writeIOSync = new Object();
public void close() {
if (zfs != null && zfs.isOpen()) {
try {
zfs.close();
} catch (IOException e) {
throw new InternalExpressionException("Unable to close zip container: "+zipContainer);
}
zfs = null;
}
}
public enum Type
{
RAW("raw", ".txt"),
TEXT("text", ".txt"),
NBT("nbt", ".nbt"),
JSON("json", ".json"),
FOLDER("folder", "");
private final String id;
private final String extension;
public static Map<String, Type> of = Arrays.stream(values()).collect(Collectors.toMap(t -> t.id, t -> t));
Type(String id, String extension)
{
this.id = id;
this.extension = extension;
}
}
public enum Reason
{
READ, CREATE, DELETE
}
public FileArgument(String resource, Type type, String zipContainer, boolean isFolder, boolean isShared, Reason reason)
{
this.resource = resource;
this.type = type;
this.zipContainer = zipContainer;
this.isFolder = isFolder;
this.isShared = isShared;
this.reason = reason;
this.zfs = null;
this.zipPath = null;
}
@Override
public String toString() {
return "path: "+resource+" zip: "+zipContainer+" type: "+type.id+" folder: "+isFolder+" shared: "+isShared+" reason: "+reason.toString();
}
public static FileArgument from(List<LazyValue> lv, Context c, boolean isFolder, Reason reason)
{
if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
Pair<String, String> resource = recognizeResource(lv.get(0).evalValue(c), isFolder);
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String typeString = shared ? origtype.substring(7) : origtype; //len(shared_)
Type type = Type.of.get(typeString);
if (type==null)
throw new InternalExpressionException("Unsupported file type: "+origtype);
if (type==Type.FOLDER && !isFolder)
throw new InternalExpressionException("Folder types are no supported for this IO function");
return new FileArgument(resource.getLeft(), type,resource.getRight(), isFolder, shared, reason);
}
public static Pair<String,String> recognizeResource(Value value, boolean isFolder)
{
String origfile = value.getString();
String[] pathElements = origfile.toLowerCase(Locale.ROOT).split("[/\\\\]+");
List<String> path = new ArrayList<>();
String zipPath = null;
for (String token : pathElements)
{
boolean isZip = token.endsWith(".zip");
if (zipPath != null && isZip) throw new InternalExpressionException(token+" indicates zip access in an already zipped location "+zipPath);
if (isZip) token = token.substring(0, token.length()-4);
token = token.replaceAll("[^A-Za-z0-9\\-+_]", "");
if (token.isEmpty()) continue;
if (isZip) token = token + ".zip";
path.add(token);
if (isZip)
{
zipPath = String.join("/", path);
path.clear();
}
}
if (path.isEmpty() && !isFolder)
throw new InternalExpressionException(
"Cannot use "+origfile+" as resource name: indicated path is empty"+((zipPath==null)?"":" in zip container "+zipPath)
);
return Pair.of(String.join("/", path), zipPath);
}
private Path resolve(String suffix)
{
if (CarpetServer.minecraft_server == null)
throw new InternalExpressionException("Accessing world files without server running");
return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+suffix);
}
private Path toPath(Module module)
{
if (!isShared && (module == null || module.getName() == null)) return null;
if (zipContainer == null)
return resolve(getDescriptor(module, resource)+(isFolder?"":type.extension));
else
{
if (zfs == null)
{
Map<String, String> env = new HashMap<>();
if (reason == Reason.CREATE) env.put("create", "true");
zipPath = resolve(getDescriptor(module, zipContainer));
try {
zfs = FileSystems.newFileSystem(URI.create("jar:"+ zipPath.toUri().toString()), env);
}
catch (FileSystemNotFoundException fsnfe)
{
return null;
}
catch (IOException e)
{
throw new ThrowStatement("Unable to open zip file: "+zipContainer, Throwables.IO_EXCEPTION);
}
}
return zfs.getPath(resource+(isFolder?"/":type.extension));
}
}
private Path moduleRootPath(Module module)
{
if (!isShared && (module == null || module.getName() == null)) return null;
return resolve(isShared?"shared":module.getName()+".data");
}
public String getDisplayPath()
{
return (isShared?"shared/":"")+(zipContainer!=null?zipContainer+"/":"")+resource+type.extension;
}
private String getDescriptor(Module module, String res)
{
if (isShared)
{
return res.isEmpty()?"shared":"shared/"+res;
}
if (module != null && module.getName() != null) // appdata
{
return module.getName()+".data"+(res==null || res.isEmpty()?"":"/"+res);
}
throw new InternalExpressionException("Invalid file descriptor: "+res);
}
public Stream<Path> listFiles(Module module)
{
Path dir = toPath(module);
if (dir == null || !Files.exists(dir)) return null;
String ext = type.extension;
try
{
return Files.list(dir).filter(path -> (type==Type.FOLDER)?path.toString().endsWith("/"):path.toString().endsWith(ext));
}
catch (IOException ignored)
{
return null;
}
}
public Stream<String> listFolder(Module module)
{
Stream<String> strings;
try { synchronized (writeIOSync)
{
Stream<Path> result;
result = listFiles(module);
if (result == null) return null;
Path rootPath = moduleRootPath(module);
if (rootPath == null) return null;
String zipComponent = (zipContainer != null) ? rootPath.relativize(zipPath).toString() : null;
strings = result.map(p -> {
if (zipContainer == null)
return rootPath.relativize(p).toString().replaceAll("[\\\\/]+", "/");
return (zipComponent + '/'+ p.toString()).replaceAll("[\\\\/]+", "/");
});
} }
finally
{
close();
}
if (type == Type.FOLDER)
return strings.map(s -> s.endsWith("/")?s.substring(0, s.length()-1):s);
return strings.map(FilenameUtils::removeExtension);
}
private void createPaths(Path file)
{
try {
if ((zipContainer == null || file.getParent() != null) &&
!Files.exists(file.getParent()) &&
Files.createDirectories(file.getParent()) == null
) throw new IOException();
} catch (IOException e) {
throw new ThrowStatement("Unable to create paths for "+file.toString(), Throwables.IO_EXCEPTION);
}
}
public boolean appendToTextFile(Module module, List<String> message)
{
try { synchronized (writeIOSync)
{
Path dataFile = toPath(module);//, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return false;
createPaths(dataFile);
OutputStream out = Files.newOutputStream(dataFile, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
{
for (String line: message)
{
writer.append(line);
if (type == Type.TEXT) writer.newLine();
}
}
} }
catch (IOException e)
{
throw new ThrowStatement("Error when writing to the file: "+e, Throwables.IO_EXCEPTION);
}
finally
{
close();
}
return true;
}
public Tag getNbtData(Module module) // aka getData
{
try { synchronized (writeIOSync) {
Path dataFile = toPath(module);
if (dataFile == null || !Files.exists(dataFile)) return null;
return readTag(dataFile);
} }
finally {
close();
}
}
//copied private method from net.minecraft.nbt.NbtIo.read()
// to read non-compound tags - these won't be compressed
public static Tag readTag(Path path)
{
try
{
return NbtIo.readCompressed(Files.newInputStream(path));
}
catch (IOException e)
{
// Copy of NbtIo.read(File) because that's now client-side only
if (!Files.exists(path))
{
return null;
}
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
{
return NbtIo.read(in);
}
catch (IOException ioException)
{
// not compressed compound tag neither uncompressed compound tag - trying any type of a tag
try (DataInputStream dataInput_1 = new DataInputStream(new BufferedInputStream(Files.newInputStream(path))))
{
byte byte_1 = dataInput_1.readByte();
if (byte_1 == 0)
{
return null;
}
else
{
dataInput_1.readUTF();
return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT);
}
}
catch (IOException ignored)
{
throw new ThrowStatement("Not a valid NBT tag in "+path.toString(), Throwables.NBT_ERROR);
}
}
}
catch (CrashException e)
{
throw new ThrowStatement("Error when reading NBT file "+path.toString(), Throwables.NBT_ERROR);
}
}
public boolean saveNbtData(Module module, Tag tag) // aka saveData
{
try { synchronized (writeIOSync) {
Path dataFile = toPath(module);
if (dataFile == null) return false;
createPaths(dataFile);
return writeTagDisk(tag, dataFile, zipContainer != null);
} }
finally
{
close();
}
}
//copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
public static boolean writeTagDisk(Tag tag_1, Path path, boolean zipped)
{
Path original = path;
try
{
if (!zipped)
{
path = new File(path.toFile().getAbsolutePath() + "_tmp").toPath();
Files.deleteIfExists(path);
}
if (tag_1 instanceof CompoundTag)
{
NbtIo.writeCompressed((CompoundTag) tag_1, Files.newOutputStream(path));
}
else
{
try (DataOutputStream dataOutputStream_1 = new DataOutputStream(Files.newOutputStream(path)))
{
dataOutputStream_1.writeByte(tag_1.getType());
if (tag_1.getType() != 0)
{
dataOutputStream_1.writeUTF("");
tag_1.write(dataOutputStream_1);
}
}
}
if (!zipped)
{
Files.deleteIfExists(original);
Files.move(path, original);
}
return true;
}
catch (IOException e)
{
throw new ThrowStatement("Unable to write tag to "+original.toString(), Throwables.IO_EXCEPTION);
}
}
public boolean dropExistingFile(Module module)
{
try { synchronized (writeIOSync)
{
Path dataFile = toPath(module);
if (dataFile == null) return false;
return Files.deleteIfExists(dataFile);
} }
catch (IOException e)
{
throw new ThrowStatement("Error while removing file: "+getDisplayPath(), Throwables.IO_EXCEPTION);
}
finally
{
close();
}
}
public List<String> listFile(Module module)
{
try { synchronized (writeIOSync) {
Path dataFile = toPath(module);
if (dataFile == null) return null;
if (!Files.exists(dataFile)) return null;
return listFileContent(dataFile);
} }
finally {
close();
}
}
public static List<String> listFileContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
break;
result.add(line.replaceAll("[\n\r]+",""));
}
return result;
}
catch (IOException e)
{
throw new ThrowStatement("Failed to read text file "+filePath.toString(), Throwables.IO_EXCEPTION);
}
}
public JsonElement readJsonFile(Module module) {
try { synchronized (writeIOSync) {
Path dataFile = toPath(module);
if (dataFile == null || !Files.exists(dataFile)) return null;
return readJsonContent(dataFile);
} }
finally {
close();
}
}
public static JsonElement readJsonContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8))
{
return new JsonParser().parse(new JsonReader(reader));
}
catch (JsonParseException e)
{
Throwable exc = e;
if(e.getCause() != null)
exc = e.getCause();
throw new ThrowStatement(MapValue.wrap(ImmutableMap.of(
StringValue.of("error"), StringValue.of(exc.getMessage()),
StringValue.of("path"), StringValue.of(filePath.toString())
)), Throwables.JSON_ERROR);
}
catch (IOException e)
{
throw new ThrowStatement("Failed to read json file content "+filePath.toString(), Throwables.IO_EXCEPTION);
}
}
}
@@ -1,33 +1,10 @@
package carpet.script.bundled;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.PositionTracker;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagReaders;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
public class FileModule extends Module
{
@@ -66,149 +43,4 @@ public class FileModule extends Module
return library;
}
//copied private method from net.minecraft.nbt.NbtIo.read()
// to read non-compound tags - these won't be compressed
public static Tag read(File file)
{
try
{
return NbtIo.readCompressed(file);
}
catch (IOException e)
{
// Copy of NbtIo.read(File) because that's now client-side only
if (!file.exists())
{
return null;
}
try (DataInputStream in = new DataInputStream(new FileInputStream(file)))
{
return NbtIo.read(in);
}
catch (IOException ioException)
{
try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
{
byte byte_1 = dataInput_1.readByte();
if (byte_1 == 0)
{
return null;
}
else
{
dataInput_1.readUTF();
return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT);
}
}
catch (IOException ignored)
{
}
return null;
}
}
}
//copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
public static boolean write(Tag tag_1, File file)
{
File file_2 = new File(file.getAbsolutePath() + "_tmp");
if (file_2.exists()) file_2.delete();
if (tag_1 instanceof CompoundTag)
{
try
{
NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
}
catch (IOException e)
{
return false;
}
}
else
{
try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2)))
{
dataOutputStream_1.writeByte(tag_1.getType());
if (tag_1.getType() != 0)
{
dataOutputStream_1.writeUTF("");
tag_1.write(dataOutputStream_1);
}
}
catch (IOException e)
{
return false;
}
}
if (file.exists()) file.delete();
if (!file.exists()) file_2.renameTo(file);
return true;
}
public static boolean appendText(Path filePath, boolean addNewLines, List<String> data)
{
try
{
OutputStream out = Files.newOutputStream(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
{
for (String line: data)
{
writer.append(line);
if (addNewLines) writer.newLine();
}
}
}
catch (IOException e)
{
return false;
}
return true;
}
public static List<String> listFileContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
break;
result.add(line.replaceAll("[\n\r]+",""));
}
return result;
}
catch (IOException e)
{
return null;
}
}
public static JsonElement readJsonContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8))
{
return new JsonParser().parse(new JsonReader(reader));
}
catch (IOException e)
{
return null;
}
}
public static Stream<Path> listFiles(Path dir, String ext)
{
boolean folderListing = ext.equals("folder");
try
{
return Files.list(dir).
filter(path -> folderListing?Files.isDirectory(path):path.toString().endsWith(ext));
}
catch (IOException ignored)
{
return null;
}
}
}
+12 -85
View File
@@ -1,112 +1,39 @@
package carpet.script.bundled;
import carpet.CarpetServer;
import carpet.script.argument.FileArgument;
import net.minecraft.nbt.Tag;
import net.minecraft.util.WorldSavePath;
import org.apache.commons.io.FilenameUtils;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public abstract class Module
{
public abstract String getName();
public abstract String getCode();
public abstract boolean isLibrary();
public final static Object writeIOSync = new Object();
public final static Map<String, String> supportedTypes = ImmutableMap.of(
"raw", ".txt",
"text", ".txt",
"nbt", ".nbt",
"json", ".json",
"folder", "folder"
);
private static String getDescriptor(Module module, String file, boolean isShared)
public static Tag getData(Module module)
{
if (isShared)
{
return file.isEmpty()?"shared":"shared/"+file;
}
if (module != null && module.getName() != null) // appdata
{
return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
}
throw new RuntimeException("Invalid file descriptor: "+file);
}
public static Tag getData(Module module, String file, boolean isShared)
{
Path dataFile = resolveResource(module, file, ".nbt", isShared);
Path dataFile = resolveResource(module);
if (dataFile == null) return null;
if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
synchronized (FileArgument.writeIOSync) { return FileArgument.readTag(dataFile); }
}
public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
public static void saveData(Module module, Tag globalState)
{
Path dataFile = resolveResource(module, file, ".nbt", isShared);
if (dataFile == null) return false;
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
Path dataFile = resolveResource(module);
if (dataFile == null) return;
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return;
synchronized (FileArgument.writeIOSync) { FileArgument.writeTagDisk(globalState, dataFile, false); }
}
public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message)
private static Path resolveResource(Module module)
{
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return false;
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
}
public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared)
{
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return false;
synchronized (writeIOSync) { return dataFile.toFile().delete(); }
}
public static List<String> listFile(Module module, String resourceName, String type, boolean isShared)
{
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return null;
if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
}
public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) {
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return null;
if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); }
}
public static Stream<String> listFolder(Module module, String resourceName, String type, boolean isShared)
{
Path dir = resolveResource(module, resourceName, null, isShared);
String ext = supportedTypes.get(type);
if (dir == null) return null;
if (!Files.exists(dir)) return null;
Stream<Path> result;
synchronized (writeIOSync) { result = FileModule.listFiles(dir, ext); }
if (result == null) return null;
Path rootPath = resolveResource(module, "", null, isShared);
Stream<String> strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
if (!ext.equals("folder"))
return strings.map(FilenameUtils::removeExtension);
return strings;
}
private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
{
if (!isShared && (module == null || module.getName() == null)) return null;
return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":ext));
if (module == null || module.getName() == null) return null; // commandline app
return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+module.getName()+".data.nbt");
}
@Override
@@ -1,5 +1,6 @@
package carpet.script.command;
import carpet.CarpetServer;
import carpet.fakes.BlockStateArgumentInterface;
import carpet.script.CarpetScriptHost;
import carpet.script.argument.FunctionArgument;
@@ -14,6 +15,7 @@ import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import carpet.utils.CarpetProfiler;
import com.google.common.collect.Lists;
import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.arguments.ArgumentType;
@@ -276,7 +278,12 @@ public abstract class CommandArgument
{
CommandArgument arg = getTypeForArgument(param, host);
if (arg.suggestionProvider != null) return argument(param, arg.getArgumentType()).suggests(arg.suggestionProvider);
return arg.needsMatching? argument(param, arg.getArgumentType()).suggests((c, b) -> arg.suggest(c, b, host)) : argument(param, arg.getArgumentType());
if (!arg.needsMatching) return argument(param, arg.getArgumentType());
String hostName = host.getName();
return argument(param, arg.getArgumentType()).suggests((ctx, b) -> {
CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
return arg.suggest(ctx, b, cHost);
});
}
protected String suffix;
@@ -365,6 +372,7 @@ public abstract class CommandArgument
{
if (customSuggester != null)
{
CarpetProfiler.ProfilerToken currentSection = CarpetProfiler.start_section(null, "Scarpet command", CarpetProfiler.TYPE.GENERAL);
Map<Value, Value> params = new HashMap<>();
for(ParsedCommandNode<ServerCommandSource> pnode : context.getNodes())
{
@@ -379,7 +387,9 @@ public abstract class CommandArgument
args.addAll(customSuggester.args);
Value response = host.handleCommand(context.getSource(), customSuggester.function, args);
if (!(response instanceof ListValue)) throw error("Custom suggester should return a list of options"+" for custom type "+suffix);
return ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList());
Collection<String> res = ((ListValue) response).getItems().stream().map(Value::getString).collect(Collectors.toList());
CarpetProfiler.end_current_section(currentSection);
return res;
}
if (needsMatching) return examples;
//return Lists.newArrayList("");
@@ -7,29 +7,43 @@ import carpet.script.Tokenizer;
import carpet.script.value.FunctionValue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
/* The expression evaluators exception class. */
public class ExpressionException extends RuntimeException implements ResolvedException
{
public final Context context;
public final Tokenizer.Token token;
public final List<FunctionValue> stack = new ArrayList<>();
private final Supplier<String> lazyMessage;
private String cachedMessage = null;
public ExpressionException(Context c, Expression e, String message)
{
super(makeMessage(c, e, null, message));
context = c;
this(c, e, Tokenizer.Token.NONE, message);
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message)
{
super(makeMessage(c, e, t, message));
context = c;
this(c, e, t, message, Collections.emptyList());
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, String message, List<FunctionValue> stack)
{
super(makeMessage(c, e, t, message));
super("Error");
this.stack.addAll(stack);
lazyMessage = () -> makeMessage(c, e, t, message);
token = t;
context = c;
}
public ExpressionException(Context c, Expression e, Tokenizer.Token t, Supplier<String> messageSupplier, List<FunctionValue> stack)
{
super("Error");
this.stack.addAll(stack);
lazyMessage = () -> makeMessage(c, e, t, messageSupplier.get());
token = t;
context = c;
}
@@ -68,4 +82,13 @@ public class ExpressionException extends RuntimeException implements ResolvedExc
}
return String.join("\n", errorMaker.apply(e, t, message));
}
@Override
public String getMessage() {
if (cachedMessage == null)
{
cachedMessage = lazyMessage.get();
}
return cachedMessage;
}
}
@@ -1,5 +1,8 @@
package carpet.script.exception;
import carpet.script.Context;
import carpet.script.Expression;
import carpet.script.Tokenizer;
import carpet.script.value.FunctionValue;
import java.util.ArrayList;
@@ -13,4 +16,20 @@ public class InternalExpressionException extends RuntimeException
{
super(message);
}
/**
* <p>Promotes this simple exception into one with context and extra information.
*
* <p>Provides a cleaner way of handling similar exceptions, in this case
* {@link InternalExpressionException} and {@link ThrowStatement}
* @param c Context
* @param e Expression
* @param token Token
* @return The new {@link ExpressionException} (or {@link ProcessedThrowStatement}),
* depending on the implementation.
*/
public ExpressionException promote(Context c, Expression e, Tokenizer.Token token)
{
return new ExpressionException(c, e, token, getMessage(), stack);
}
}
@@ -0,0 +1,20 @@
package carpet.script.exception;
import java.util.List;
import carpet.script.Context;
import carpet.script.Expression;
import carpet.script.Tokenizer.Token;
import carpet.script.value.FunctionValue;
import carpet.script.value.Value;
public class ProcessedThrowStatement extends ExpressionException {
public final Throwables thrownExceptionType;
public final Value data;
public ProcessedThrowStatement(Context c, Expression e, Token token, List<FunctionValue> stack, Throwables thrownExceptionType, Value data) {
super(c, e, token, () -> "Unhandled "+thrownExceptionType.getId()+" exception: "+data.getString(), stack);
this.thrownExceptionType = thrownExceptionType;
this.data = data;
}
}
@@ -1,11 +1,54 @@
package carpet.script.exception;
import carpet.script.Context;
import carpet.script.Expression;
import carpet.script.Tokenizer.Token;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
public class ThrowStatement extends ExitStatement
public class ThrowStatement extends InternalExpressionException
{
public ThrowStatement(Value value)
private final Throwables type;
private final Value data;
/**
* Creates a throw exception from a value, and assigns it a specified message.
* <p>To be used when throwing from Scarpet's {@code throw} function
* @param data The value to pass
* @param type Exception type
*/
public ThrowStatement(Value data, Throwables type)
{
super(value);
super(type.getId());
this.data = data;
this.type = type;
}
public ThrowStatement(Value data, Throwables parent, String subtype)
{
super(subtype);
this.data = data;
this.type = new Throwables(subtype, parent);
}
/**
* Creates a throw exception.<br>
* Conveniently creates a value from the {@code value} String
* to be used easily in Java code
* @param message The message to display when not handled
* @param type An {@link Throwables} containing the inheritance data
* for this exception. When throwing from Java,
* those exceptions should be pre-registered.
*/
public ThrowStatement(String message, Throwables type)
{
super(type.getId());
this.data = StringValue.of(message);
this.type = type;
}
@Override
public ExpressionException promote(Context c, Expression e, Token token) {
return new ProcessedThrowStatement(c, e, token, stack, type, data);
}
}
@@ -0,0 +1,89 @@
package carpet.script.exception;
import java.util.HashMap;
import java.util.Map;
/**
* This class contains default Scarpet catchable types, as well as their inheritance and
* methods to check whether filters are compatible with those.
*/
public class Throwables {
private final String id;
private final Throwables parent;
private static final Map<String, Throwables> byId = new HashMap<>();
public static final Throwables THROWN_EXCEPTION_TYPE = register("exception", null);
public static final Throwables VALUE_EXCEPTION = register("value_exception", THROWN_EXCEPTION_TYPE);
public static final Throwables UNKNOWN_ITEM = register("unknown_item", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_BLOCK = register("unknown_block", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_BIOME = register("unknown_biome", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_SOUND = register("unknown_sound", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_PARTICLE = register("unknown_particle", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_POI = register("unknown_poi", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_DIMENSION = register("unknown_dimension", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_STRUCTURE = register("unknown_structure", VALUE_EXCEPTION);
public static final Throwables UNKNOWN_CRITERION = register("unknown_criterion", VALUE_EXCEPTION);
public static final Throwables IO_EXCEPTION = register("io_exception", THROWN_EXCEPTION_TYPE);
public static final Throwables NBT_ERROR = register("nbt_error", IO_EXCEPTION);
public static final Throwables JSON_ERROR = register("json_error", IO_EXCEPTION);
public static final Throwables USER_DEFINED = register("user_exception", THROWN_EXCEPTION_TYPE);
/**
* Creates an exception and registers it to be used as parent for
* user defined exceptions in Scarpet's throw function.
* <p>Scarpet exceptions should have a top-level parent being {@link Throwables#THROWN_EXCEPTION_TYPE}
* @param id The value for the exception as a {@link String}.
* @param parent The parent of the exception being created, or <code>null</code> if top-level
* @return The created exception
*/
public static Throwables register(String id, Throwables parent)
{
Throwables exc = new Throwables(id, parent);
byId.put(id, exc);
return exc;
}
/**
* Creates a new exception.
* <p>Not suitable for creating exceptions that can't be caught.
* Use an {@link InternalExpressionException} for that
* @param id The exception's value as a {@link String}
*/
public Throwables(String id, Throwables parent)
{
this.id = id;
this.parent = parent;
}
public static Throwables getTypeForException(String type)
{
Throwables properType = byId.get(type);
if (properType == null) throw new InternalExpressionException("Unknown exception type: "+type);
return properType;
}
/**
* Checks whether the given filter matches an instance of this exception, by checking equality
* with itself and possible parents.
* @param filter The type to check against
* @return Whether or not the given value matches this exception's hierarchy
*/
public boolean isRelevantFor(String filter)
{
return (id.equals(filter) || (parent != null && parent.isRelevantFor(filter)));
}
public boolean isUserException()
{
return this == USER_DEFINED || parent == USER_DEFINED;
}
/**
* Returns the throwable type
* @return The type of this exception
*/
public String getId() {
return id;
}
}
@@ -60,9 +60,9 @@ public class Arithmetic {
expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2));
expression.addMathematicalUnaryFunction("sqrt", Math::sqrt);
expression.addMathematicalUnaryFunction("abs", Math::abs);
expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d));
expression.addMathematicalUnaryFunction("floor", Math::floor);
expression.addMathematicalUnaryFunction("ceil", Math::ceil);
expression.addMathematicalUnaryIntFunction("round", Math::round);
expression.addMathematicalUnaryIntFunction("floor", n -> (long)Math.floor(n));
expression.addMathematicalUnaryIntFunction("ceil", n -> (long)Math.ceil(n));
expression.addLazyFunction("mandelbrot", 3, (c, t, lv) -> {
double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble();
@@ -5,8 +5,17 @@ import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ProcessedThrowStatement;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.value.ListValue;
import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import com.google.common.collect.ImmutableMap;
import java.util.stream.Collectors;
public class ControlFlow {
public static void apply(Expression expression) // public just to get the javadoc right
@@ -42,26 +51,98 @@ public class ControlFlow {
expression.addFunction("exit", (lv) -> { throw new ExitStatement(lv.size()==0?Value.NULL:lv.get(0)); });
expression.addFunction("throw", (lv)-> {throw new ThrowStatement(lv.size()==0?Value.NULL:lv.get(0)); });
expression.addLazyFunction("throw", -1, (c, t, lv)->
{
switch (lv.size())
{
case 0:
throw new ThrowStatement(Value.NULL, Throwables.USER_DEFINED);
case 1:
throw new ThrowStatement(lv.get(0).evalValue(c), Throwables.USER_DEFINED );
case 2:
throw new ThrowStatement(lv.get(1).evalValue(c), Throwables.getTypeForException(lv.get(0).evalValue(c).getString()));
case 3:
throw new ThrowStatement(lv.get(2).evalValue(c), Throwables.getTypeForException(lv.get(1).evalValue(c).getString()), lv.get(0).evalValue(c).getString());
default:
throw new InternalExpressionException("throw() can't accept more than 3 parameters");
}
});
expression.addLazyFunction("try", -1, (c, t, lv) ->
{
if (lv.size()==0)
throw new InternalExpressionException("'try' needs at least an expression block");
throw new InternalExpressionException("'try' needs at least an expression block, and either a catch_epr, or a number of pairs of filters and catch_expr");
try
{
Value retval = lv.get(0).evalValue(c, t);
return (c_, t_) -> retval;
}
catch (ThrowStatement ret)
catch (ProcessedThrowStatement ret)
{
if (lv.size() == 1)
{
if (!ret.thrownExceptionType.isUserException())
throw ret;
return (c_, t_) -> Value.NULL;
}
if (lv.size() > 3 && lv.size() % 2 == 0)
{
throw new InternalExpressionException("Try-catch block needs the code to run, and either a catch expression for user thrown exceptions, or a number of pairs of filters and catch expressions");
}
Value val = null; // This is always assigned at some point, just the compiler doesn't know
LazyValue __ = c.getVariable("_");
c.setVariable("_", (__c, __t) -> ret.retval.reboundedTo("_"));
Value val = lv.get(1).evalValue(c, t);
c.setVariable("_",__);
return (c_, t_) -> val;
c.setVariable("_", (__c, __t) -> ret.data.reboundedTo("_"));
LazyValue _trace = c.getVariable("_trace");
c.setVariable("_trace", (__c, __t) -> MapValue.wrap(ImmutableMap.of(
StringValue.of("stack"), ListValue.wrap(ret.stack.stream().map(f -> ListValue.of(
StringValue.of(f.getModule().getName()),
StringValue.of(f.getString()),
NumericValue.of(f.getToken().lineno+1),
NumericValue.of(f.getToken().linepos+1)
)).collect(Collectors.toList())),
StringValue.of("locals"), MapValue.wrap(ret.context.variables.entrySet().stream().filter(e -> !e.getKey().equals("_trace")).collect(Collectors.toMap(
e -> StringValue.of(e.getKey()),
e -> e.getValue().evalValue(ret.context)
))),
StringValue.of("token"), ListValue.of(
StringValue.of(ret.token.surface),
NumericValue.of(ret.token.lineno+1),
NumericValue.of(ret.token.linepos+1)
)
)));
if (lv.size() == 2)
{
if (ret.thrownExceptionType.isUserException())
val = lv.get(1).evalValue(c, t);
}
else
{
int pointer = 1;
while (pointer < lv.size() -1)
{
if (ret.thrownExceptionType.isRelevantFor(lv.get(pointer).evalValue(c).getString()))
{
val = lv.get(pointer + 1).evalValue(c, t);
break;
}
pointer += 2;
}
}
c.setVariable("_", __);
if (_trace != null)
c.setVariable("_trace", _trace);
else
c.delVariable("_trace");
if (val == null) // not handled
{
throw ret;
}
Value retval = val;
return (c_, t_) -> retval;
}
});
}
@@ -119,7 +119,7 @@ public class Loops {
c.setVariable("_i", (cc, tt) -> new NumericValue(doYouReally).bindTo("_i"));
try
{
result.add(expr.evalValue(c));
result.add(expr.evalValue(c, t));
}
catch (BreakStatement | ContinueStatement stmt)
{
@@ -48,7 +48,7 @@ public class Sys {
return (cc, tt) -> Value.FALSE;
}
}
Value retval = new NumericValue(v.getBoolean());
Value retval = v.getBoolean()?Value.TRUE:Value.FALSE;
return (cc, tt) -> retval;
});
@@ -56,7 +56,9 @@ public class Sys {
{
if (v instanceof NumericValue)
{
return v;
NumericValue num = (NumericValue)v;
if (num.isInteger()) return new NumericValue(num.getLong());
return new NumericValue(num.getDouble());
}
try
{
@@ -208,6 +210,10 @@ public class Sys {
Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble());
return (cc, tt) -> retval;
});
expression.addLazyFunction("reset_seed", 1, (c, t, lv) -> {
boolean gotIt = c.host.resetRandom(NumericValue.asNumber(lv.get(0).evalValue(c)).getLong());
return gotIt?LazyValue.TRUE:LazyValue.FALSE;
});
expression.addLazyFunction("perlin", -1, (c, t, lv) ->
{
@@ -0,0 +1,16 @@
package carpet.script.utils;
import carpet.script.exception.InternalExpressionException;
import java.util.Locale;
public class InputValidator {
public static String validateSimpleString(String input, boolean strict)
{
String simplified = input.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9+_]", "");
if (simplified.isEmpty() || (strict && !simplified.equals(input)))
throw new InternalExpressionException("simple name can only contain numbers, letter and _");
return simplified;
}
}
@@ -4,6 +4,8 @@ import carpet.CarpetSettings;
import carpet.network.ServerNetworkHandler;
import carpet.script.CarpetContext;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.language.Sys;
import carpet.script.value.BlockValue;
import carpet.script.value.EntityValue;
@@ -156,7 +158,7 @@ public class ShapeDispatcher
}
catch (CommandSyntaxException e)
{
throw new InternalExpressionException("No such particle: "+name);
throw new ThrowStatement(name, Throwables.UNKNOWN_PARTICLE);
}
particleCache.put(name, particle);
return particle;
@@ -416,6 +418,8 @@ public class ShapeDispatcher
put("facing", new StringValue("player")).
put("raise", new NumericValue(0)).
put("tilt", new NumericValue(0)).
put("lean", new NumericValue(0)).
put("turn", new NumericValue(0)).
put("indent", new NumericValue(0)).
put("height", new NumericValue(0)).
put("align", new StringValue("center")).
@@ -437,6 +441,8 @@ public class ShapeDispatcher
Direction facing;
float raise;
float tilt;
float lean;
float turn;
float size;
float indent;
int align;
@@ -485,6 +491,8 @@ public class ShapeDispatcher
raise = NumericValue.asNumber(options.getOrDefault("raise", optional.get("raise"))).getFloat();
tilt = NumericValue.asNumber(options.getOrDefault("tilt", optional.get("tilt"))).getFloat();
lean = NumericValue.asNumber(options.getOrDefault("lean", optional.get("lean"))).getFloat();
turn = NumericValue.asNumber(options.getOrDefault("turn", optional.get("turn"))).getFloat();
indent = NumericValue.asNumber(options.getOrDefault("indent", optional.get("indent"))).getFloat();
height = NumericValue.asNumber(options.getOrDefault("height", optional.get("height"))).getFloat();
@@ -517,6 +525,8 @@ public class ShapeDispatcher
if (facing!= null) hash ^= facing.hashCode(); hash *= 1099511628211L;
hash ^= Float.hashCode(raise); hash *= 1099511628211L;
hash ^= Float.hashCode(tilt); hash *= 1099511628211L;
hash ^= Float.hashCode(lean); hash *= 1099511628211L;
hash ^= Float.hashCode(turn); hash *= 1099511628211L;
hash ^= Float.hashCode(indent); hash *= 1099511628211L;
hash ^= Float.hashCode(height); hash *= 1099511628211L;
hash ^= Float.hashCode(size); hash *= 1099511628211L;
@@ -1,6 +1,7 @@
package carpet.script.utils;
import carpet.CarpetSettings;
import carpet.utils.CarpetProfiler;
import com.mojang.blaze3d.systems.RenderSystem;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
@@ -49,6 +50,7 @@ public class ShapesRenderer
public void render(Camera camera, float partialTick)
{
CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
//Camera camera = this.client.gameRenderer.getCamera();
ClientWorld iWorld = this.client.world;
RegistryKey<World> dimensionType = iWorld.getRegistryKey();
@@ -120,14 +122,17 @@ public class ShapesRenderer
RenderSystem.defaultBlendFunc();
RenderSystem.enableTexture();
RenderSystem.shadeModel(7424);
CarpetProfiler.end_current_section(token);
}
public void addShapes(ListTag tag)
{
CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
for (int i=0, count = tag.size(); i < count; i++)
{
addShape(tag.getCompound(i));
}
CarpetProfiler.end_current_section(token);
}
public void addShape(CompoundTag tag)
@@ -169,12 +174,14 @@ public class ShapesRenderer
public void renewShapes()
{
CarpetProfiler.ProfilerToken token = CarpetProfiler.start_section(null, "Scarpet client", CarpetProfiler.TYPE.GENERAL);
synchronized (shapes)
{
shapes.values().forEach(el -> el.values().forEach(shape -> {
shape.expiryTick++;
}));
}
CarpetProfiler.end_current_section(token);
}
public abstract static class RenderedShape<T extends ShapeDispatcher.ExpiringShape>
@@ -278,6 +285,14 @@ public class ShapesRenderer
{
RenderSystem.rotatef(shape.tilt, 0.0f, 0.0f, 1.0f);
}
if (shape.lean!=0.0f)
{
RenderSystem.rotatef(shape.lean, 1.0f, 0.0f, 0.0f);
}
if (shape.turn!=0.0f)
{
RenderSystem.rotatef(shape.turn, 0.0f, 1.0f, 0.0f);
}
RenderSystem.translatef(-10*shape.indent, -10*shape.height-9, (float) (-10*renderEpsilon)-10*shape.raise);
//if (visibleThroughWalls) RenderSystem.disableDepthTest();
RenderSystem.scalef(-1.0F, 1.0F, 1.0F);
@@ -4,11 +4,13 @@ import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.CarpetContext;
import carpet.script.CarpetScriptHost;
import carpet.script.value.BooleanValue;
import carpet.script.value.ListValue;
import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import carpet.settings.ParsedRule;
import carpet.settings.SettingsManager;
import com.sun.management.OperatingSystemMXBean;
@@ -16,6 +18,7 @@ import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.util.WorldSavePath;
import net.minecraft.world.GameRules;
import net.minecraft.world.WorldProperties;
import java.lang.management.ManagementFactory;
import java.nio.file.Path;
@@ -49,6 +52,12 @@ public class SystemInfo {
String tlf = serverPath.getName(nodeCount-2).toString();
return StringValue.of(tlf);
});
put("world_dimensions", c -> ListValue.wrap(c.s.getMinecraftServer().getWorldRegistryKeys().stream().map(k -> ValueConversions.of(k.getValue())).collect(Collectors.toList())));
put("world_spawn_point", c -> {
WorldProperties prop = c.s.getMinecraftServer().getOverworld().getLevelProperties();
return ListValue.of(NumericValue.of(prop.getSpawnX()), NumericValue.of(prop.getSpawnY()), NumericValue.of(prop.getSpawnZ()));
});
put("game_difficulty", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getDifficulty().getName()));
put("game_hardcore", c -> new NumericValue(c.s.getMinecraftServer().getSaveProperties().isHardcore()));
put("game_storage_format", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getFormatName(c.s.getMinecraftServer().getSaveProperties().getVersion())));
@@ -57,7 +66,19 @@ public class SystemInfo {
put("game_view_distance", c -> new NumericValue(c.s.getMinecraftServer().getPlayerManager().getViewDistance()));
put("game_mod_name", c -> StringValue.of(c.s.getMinecraftServer().getServerModName()));
put("game_version", c -> StringValue.of(c.s.getMinecraftServer().getVersion()));
put("game_target", c -> StringValue.of(SharedConstants.getGameVersion().getReleaseTarget()));
put("game_protocol", c -> NumericValue.of(SharedConstants.method_31372()));
put("game_major_target", c -> {
String [] vers = SharedConstants.getGameVersion().getReleaseTarget().split("\\.");
return NumericValue.of((vers.length > 1)?Integer.parseInt(vers[1]):0);
});
put("game_minor_target", c -> {
String [] vers = SharedConstants.getGameVersion().getReleaseTarget().split("\\.");
return NumericValue.of((vers.length > 2)?Integer.parseInt(vers[2]):0);
});
put("game_stable", c -> BooleanValue.of(SharedConstants.getGameVersion().isStable()));
put("game_data_version", c->NumericValue.of(SharedConstants.getGameVersion().getWorldVersion()));
put("game_pack_version", c->NumericValue.of(SharedConstants.getGameVersion().getPackVersion()));
put("server_ip", c -> StringValue.of(c.s.getMinecraftServer().getServerIp()));
put("server_whitelisted", c -> new NumericValue(c.s.getMinecraftServer().isEnforceWhitelist()));
@@ -157,5 +157,5 @@ public class WorldTools
@Override public void stop() { }
}
private static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener();
public static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener();
}
@@ -2,6 +2,9 @@ package carpet.script.value;
import carpet.script.CarpetContext;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState;
@@ -73,7 +76,7 @@ public class BlockValue extends Value
catch (CommandSyntaxException ignored)
{
}
throw new InternalExpressionException("Cannot parse block: "+str);
throw new ThrowStatement(str, Throwables.UNKNOWN_BLOCK);
}
public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos)
@@ -0,0 +1,63 @@
package carpet.script.value;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import net.minecraft.nbt.ByteTag;
import net.minecraft.nbt.Tag;
public class BooleanValue extends NumericValue
{
public static final BooleanValue FALSE = new BooleanValue(false);
public static final BooleanValue TRUE = new BooleanValue(true);
boolean boolValue;
private BooleanValue(boolean boolval) {
super(boolval);
boolValue = boolval;
}
public static BooleanValue of(boolean value)
{
return value?TRUE:FALSE;
}
@Override
public String getString() {
return boolValue?"true":"false";
}
@Override
public String getPrettyString() {
return getString();
}
@Override
public String getTypeString() {
return "bool";
}
@Override
public Value clone() {
return new BooleanValue(boolValue);
}
@Override
public int hashCode() {
return Boolean.hashCode(boolValue);
}
@Override
public Tag toTag(boolean force) {
return ByteTag.of(boolValue);
}
@Override
public JsonElement toJson() {
return new JsonPrimitive(boolValue);
}
@Override
public boolean isInteger() {
return true;
}
}
@@ -56,6 +56,7 @@ import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
@@ -98,6 +99,12 @@ public class EntityValue extends Value
entity = e;
}
public static Value of(Entity e)
{
if (e == null) return Value.NULL;
return new EntityValue(e);
}
private static final Map<String, EntitySelector> selectorCache = new HashMap<>();
public static Collection<? extends Entity > getEntitiesFromSelector(ServerCommandSource source, String selector)
{
@@ -416,7 +423,7 @@ public class EntityValue extends Value
put("motion_z", (e, a) -> new NumericValue(e.getVelocity().z));
put("on_ground", (e, a) -> new NumericValue(e.isOnGround()));
put("name", (e, a) -> new StringValue(e.getName().getString()));
put("display_name", (e, a) -> new StringValue(e.getDisplayName().getString()));
put("display_name", (e, a) -> new FormattedTextValue(e.getDisplayName()));
put("command_name", (e, a) -> new StringValue(e.getEntityName()));
put("custom_name", (e, a) -> e.hasCustomName()?new StringValue(e.getCustomName().getString()):Value.NULL);
put("type", (e, a) -> new StringValue(nameFromRegistryId(Registry.ENTITY_TYPE.getId(e.getType()))));
@@ -429,7 +436,7 @@ public class EntityValue extends Value
put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList())));
put("scoreboard_tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList())));
put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTagsFor(e.getType()).stream().map(ValueConversions::of).collect(Collectors.toList())));
put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTags().entrySet().stream().filter(entry -> entry.getValue().contains(e.getType())).map(entry -> ValueConversions.of(entry.getKey())).collect(Collectors.toList())));
// deprecated
put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString())));
@@ -1307,7 +1314,7 @@ public class EntityValue extends Value
else if (v instanceof ListValue)
{
List<Value> lv = ((ListValue) v).getItems();
if (lv.size() >= 1 && lv.size() <= 5)
if (lv.size() >= 1 && lv.size() <= 6)
{
String effectName = lv.get(0).getString();
StatusEffect effect = Registry.STATUS_EFFECT.get(new Identifier(effectName));
@@ -1376,6 +1383,19 @@ public class EntityValue extends Value
}
});
put("swing", (e, v) -> {
if (e instanceof LivingEntity)
{
Hand hand = Hand.MAIN_HAND;
if (v != null)
{
String handString = v.getString().toLowerCase(Locale.ROOT);
if (handString.equals("offhand") || handString.equals("off_hand")) hand = Hand.OFF_HAND;
}
((LivingEntity)e).swingHand(hand, true);
}
});
put("silent",(e,v)-> e.setSilent(v.getBoolean()));
put("gravity",(e,v)-> e.setNoGravity(!v.getBoolean()));
@@ -215,7 +215,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
}
Value retVal;
boolean rethrow = false;
try
{
retVal = body.evalValue(newFrame, type); // todo not sure if we need to propagete type / consider boolean context in defined functions - answer seems ye
@@ -228,15 +227,6 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
{
retVal = returnStatement.retval;
}
catch (ThrowStatement throwStatement) // might not be really necessary
{
retVal = throwStatement.retval;
rethrow = true;
}
if (rethrow)
{
throw new ThrowStatement(retVal);
}
Value otherRetVal = retVal;
return (cc, tt) -> otherRetVal;
}
@@ -341,7 +341,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf
int from = normalizeIndex(fromDesc, size);
if (toDesc == null)
return new ListValue((Collection<? extends Value>) getItems().subList(from, size));
int to = normalizeIndex(toDesc, size);
int to = normalizeIndex(toDesc, size+1);
if (from > to)
return ListValue.of();
return new ListValue((Collection<? extends Value>) getItems().subList(from, to));
@@ -7,6 +7,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -16,7 +17,7 @@ import java.util.stream.Collectors;
public class MapValue extends AbstractListValue implements ContainerValueInterface
{
private Map<Value, Value> map;
private final Map<Value, Value> map;
private MapValue()
{
@@ -268,7 +269,9 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa
public JsonElement toJson()
{
JsonObject jsonMap = new JsonObject();
map.forEach((k, v) -> jsonMap.add(k.getString(), v.toJson()));
List<Value> keys = new ArrayList<>(map.keySet());
Collections.sort(keys);
keys.forEach( k -> jsonMap.add(k.getString(), map.get(k).toJson()));
return jsonMap;
}
}
@@ -4,6 +4,8 @@ import carpet.fakes.InventoryBearerInterface;
import carpet.script.CarpetContext;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.script.utils.EquipmentInventory;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -297,7 +299,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
}
catch (CommandSyntaxException e)
{
throw new InternalExpressionException("Incorrect item: "+itemString);
throw new ThrowStatement(itemString, Throwables.UNKNOWN_ITEM);
}
}
@@ -9,6 +9,7 @@ import net.minecraft.nbt.Tag;
public class NullValue extends NumericValue // TODO check nonsingleton code
{
public static final NullValue NULL = new NullValue();
@Override
public String getString()
{
@@ -32,7 +33,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code
{
return new NullValue();
}
public NullValue() {super(0.0D);}
private NullValue() {super(false);}
@Override
public boolean equals(final Object o)
+9 -9
View File
@@ -15,12 +15,12 @@ import java.util.stream.Collectors;
public abstract class Value implements Comparable<Value>, Cloneable
{
public static NumericValue FALSE = new NumericValue(0);
public static NumericValue TRUE = new NumericValue(1);
public static NumericValue ZERO = FALSE;
public static NumericValue ONE = TRUE;
public static NumericValue FALSE = BooleanValue.FALSE;
public static NumericValue TRUE = BooleanValue.TRUE;
public static NumericValue ZERO = new NumericValue(0);
public static NumericValue ONE = new NumericValue(1);
public static NullValue NULL = new NullValue();
public static NullValue NULL = NullValue.NULL;
public String boundVariable;
@@ -141,12 +141,12 @@ public abstract class Value implements Comparable<Value>, Cloneable
public void assertAssignable()
{
if (boundVariable == null || boundVariable.startsWith("_"))
if (boundVariable == null)// || boundVariable.startsWith("_"))
{
if (boundVariable != null)
/*if (boundVariable != null)
{
throw new InternalExpressionException(boundVariable+ " cannot be assigned a new value");
}
}*/
throw new InternalExpressionException(getString()+ " is not a variable");
}
@@ -186,7 +186,7 @@ public abstract class Value implements Comparable<Value>, Cloneable
int size = value.length();
int from = ListValue.normalizeIndex(fromDesc, size);
if (toDesc == null) return new StringValue(value.substring(from));
int to = ListValue.normalizeIndex(toDesc, size);
int to = ListValue.normalizeIndex(toDesc, size+1);
if (from > to) return StringValue.EMPTY;
return new StringValue(value.substring(from, to));
}
@@ -2,6 +2,8 @@ package carpet.script.value;
import carpet.fakes.BlockPredicateInterface;
import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.ThrowStatement;
import carpet.script.exception.Throwables;
import carpet.utils.BlockInfo;
import net.minecraft.block.BlockState;
import net.minecraft.block.MaterialColor;
@@ -158,7 +160,7 @@ public class ValueConversions
}
}
if (dim == null)
throw new InternalExpressionException("Incorrect dimension string: "+dimString);
throw new ThrowStatement(dimString, Throwables.UNKNOWN_DIMENSION);
return server.getWorld(dim);
}
}
@@ -318,10 +318,12 @@ public class SettingsManager
}
server.send(new ServerTask(this.server.getTicks(), () ->
{
for (ServerPlayerEntity entityplayermp : server.getPlayerManager().getPlayerList())
{
server.getCommandManager().sendCommandTree(entityplayermp);
try {
for (ServerPlayerEntity entityplayermp : server.getPlayerManager().getPlayerList()) {
server.getCommandManager().sendCommandTree(entityplayermp);
}
}
catch (NullPointerException ignored) {}
}));
}
+62 -58
View File
@@ -1,6 +1,5 @@
package carpet.utils;
import carpet.CarpetSettings;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import net.minecraft.block.entity.BlockEntityType;
@@ -9,6 +8,7 @@ import net.minecraft.entity.EntityType;
import net.minecraft.server.MinecraftServer;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
@@ -29,9 +29,13 @@ public class CarpetProfiler
private static ServerCommandSource currentRequester = null;
public static int tick_health_requested = 0;
private static int tick_health_elapsed = 0;
private static TYPE test_type = TYPE.NONE; //1 for ticks, 2 for entities;
private static TYPE test_type = TYPE.NONE; //1 for ticks, 2 for entities
private static long current_tick_start = 0;
private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks",};
private static final String[] GENERAL_SECTIONS = {"Network", "Autosave", "Async Tasks", "Datapacks", "Carpet"};
private static final String[] SCARPET_SECTIONS = {
"Scarpet run", "Scarpet events", "Scarpet schedule",
"Scarpet command", "Scarpet load", "Scarpet app data", "Scarpet client"
};
private static final String[] SECTIONS = {
"Spawning and Random Ticks", "Ticket Manager","Unloading",
"Blocks", "Entities", "Block Entities",
@@ -53,28 +57,15 @@ public class CarpetProfiler
public final long start;
public final World world;
public ProfilerToken(TYPE type, Object section, World world, long start)
public ProfilerToken(TYPE type, Object section, World world)
{
this.type = type;
this.section = section;
this.start = start;
this.start = System.nanoTime();
this.world = world;
}
}
private static String getSectionString(World world, String section)
{
return String.format("%s.%s%s",
world.getRegistryKey().getValue().getPath(),
section, // ^ getDImension getId
world.isClient ? " (Client)" : "");
}
private static String getDimName(RegistryKey<World> world)
{
return world.getValue().getPath();
}
public static void prepare_tick_report(ServerCommandSource source, int ticks)
{
//maybe add so it only spams the sending player, but honestly - all may want to see it
@@ -83,17 +74,19 @@ public class CarpetProfiler
ENTITY_TIMES.clear();
test_type = TYPE.GENERAL;
SECTION_STATS.put("tick", 0L);
SECTION_STATS.put("Network", 0L);
SECTION_STATS.put("Autosave", 0L);
for (String section : GENERAL_SECTIONS)
{
SECTION_STATS.put(section, 0L);
}
for (String section : SCARPET_SECTIONS)
{
SECTION_STATS.put(section, 0L);
}
for (RegistryKey<World> level : source.getMinecraftServer().getWorldRegistryKeys())
{
for (String section : SECTIONS)
{
SECTION_STATS.put(getDimName(level) + "." + section, 0L);
SECTION_STATS.put(level.getValue() + "." + section, 0L);
}
}
@@ -121,21 +114,21 @@ public class CarpetProfiler
{
if (tick_health_requested == 0L || test_type != TYPE.GENERAL || current_tick_start == 0)
return null;
return new ProfilerToken(type, name, world, System.nanoTime());
return new ProfilerToken(type, name, world);
}
public static ProfilerToken start_entity_section(World world, Entity e, TYPE type)
{
if (tick_health_requested == 0L || test_type != TYPE.ENTITY || current_tick_start == 0)
return null;
return new ProfilerToken(type, e.getType(), world, System.nanoTime());
return new ProfilerToken(type, e.getType(), world);
}
public static ProfilerToken start_block_entity_section(World world, BlockEntity be, TYPE type)
{
if (tick_health_requested == 0L || test_type != TYPE.ENTITY || current_tick_start == 0)
return null;
return new ProfilerToken(type, be.getType(), world, System.nanoTime());
return new ProfilerToken(type, be.getType(), world);
}
public static void end_current_section(ProfilerToken tok)
@@ -145,14 +138,11 @@ public class CarpetProfiler
long end_time = System.nanoTime();
if (tok.type == TYPE.GENERAL)
{
String current_section = (tok.world == null) ? (String) tok.section :
getSectionString(tok.world, (String)tok.section);
if (SECTION_STATS.get(current_section) == null)
{
CarpetSettings.LOG.error("Current section: "+current_section);
}
else
SECTION_STATS.put(current_section, SECTION_STATS.get(current_section) + end_time - tok.start);
World world = tok.world;
String current_section = (world == null) ?
(String) tok.section :
String.format("%s.%s%s", world.getRegistryKey().getValue(), tok.section, world.isClient ? " (Client)" : "");
SECTION_STATS.put(current_section, SECTION_STATS.getOrDefault(current_section, 0L) + end_time - tok.start);
}
}
@@ -171,12 +161,10 @@ public class CarpetProfiler
current_tick_start = System.nanoTime();
}
public static void end_tick_profiling(MinecraftServer server, ProfilerToken postTasks)
public static void end_tick_profiling(MinecraftServer server)
{
if (current_tick_start == 0L)
return;
if (postTasks != null)
end_current_section(postTasks);
SECTION_STATS.put("tick", SECTION_STATS.get("tick") + System.nanoTime() - current_tick_start);
tick_health_elapsed--;
if (tick_health_elapsed <= 0)
@@ -215,7 +203,7 @@ public class CarpetProfiler
long total_tick_time = SECTION_STATS.get("tick");
double divider = 1.0D / tick_health_requested / 1000000;
Messenger.m(currentRequester, "w ");
Messenger.m(currentRequester, String.format("gi Avg. server tick time: %.3fms", divider * total_tick_time));
Messenger.m(currentRequester, "wb Average tick time: ", String.format("yb %.3fms", divider * total_tick_time));
long accumulated = 0L;
for (String section : GENERAL_SECTIONS)
@@ -224,17 +212,26 @@ public class CarpetProfiler
if (amount > 0.01)
{
accumulated += SECTION_STATS.get(section);
Messenger.m(currentRequester, String.format("gi %s: %.3fms", section, amount));
Messenger.m(currentRequester, "w "+section+": ", String.format("y %.3fms", amount));
}
}
for (String section : SCARPET_SECTIONS)
{
double amount = divider * SECTION_STATS.get(section);
if (amount > 0.01)
{
Messenger.m(currentRequester, "gi "+section+": ", String.format("di %.3fms", amount));
}
}
for (RegistryKey<World> dim : server.getWorldRegistryKeys())
{
String dimension = getDimName(dim);
Identifier dimensionId = dim.getValue();
boolean hasSomethin = false;
for (String section : SECTIONS)
{
double amount = divider * SECTION_STATS.get(dimension + "." + section);
double amount = divider * SECTION_STATS.getOrDefault(dimensionId + "." + section, 0L);
if (amount > 0.01)
{
hasSomethin = true;
@@ -245,15 +242,16 @@ public class CarpetProfiler
{
continue;
}
Messenger.m(currentRequester, "gi "+dimension + ":");
Messenger.m(currentRequester, "wb "+(dimensionId.getNamespace().equals("minecraft")?dimensionId.getPath():dimensionId.toString()) + ":");
for (String section : SECTIONS)
{
double amount = divider * SECTION_STATS.get(dimension + "." + section);
double amount = divider * SECTION_STATS.getOrDefault(dimensionId + "." + section, 0L);
if (amount > 0.01)
{
if (!(section.endsWith("(Client)")))
accumulated += SECTION_STATS.get(dimension + "." + section);
Messenger.m(currentRequester, String.format("gi - %s: %.3fms", section, amount));
boolean cli = section.endsWith("(Client)");
if (!cli)
accumulated += SECTION_STATS.get(dimensionId + "." + section);
Messenger.m(currentRequester, String.format("%s - %s: ", cli?"gi":"w", section), String.format("%s %.3fms", cli?"di":"y", amount));
}
}
}
@@ -265,20 +263,22 @@ public class CarpetProfiler
private static String sectionName(Pair<World,Object> section)
{
String name;
Identifier id;
if (section.getValue() instanceof EntityType)
{
name = Registry.ENTITY_TYPE.getId((EntityType<?>) section.getValue()).getPath();
id = Registry.ENTITY_TYPE.getId((EntityType<?>) section.getValue());
}
else
{
name = Registry.BLOCK_ENTITY_TYPE.getId((BlockEntityType<?>) section.getValue()).getPath();
id = Registry.BLOCK_ENTITY_TYPE.getId((BlockEntityType<?>) section.getValue());
}
String name = "minecraft".equals(id.getNamespace())?id.getPath():id.toString();
if (section.getKey().isClient)
{
name += " (client)";
}
String dim = section.getKey().getRegistryKey().getValue().getPath();
Identifier dimkey = section.getKey().getRegistryKey().getValue();
String dim = "minecraft".equals(dimkey.getNamespace())?dimkey.getPath():dimkey.toString();
return name+" in "+dim;
}
@@ -289,31 +289,35 @@ public class CarpetProfiler
long total_tick_time = SECTION_STATS.get("tick");
double divider = 1.0D / tick_health_requested / 1000000;
double divider_1 = 1.0D / (tick_health_requested - 1) / 1000000;
Messenger.m(currentRequester, "gi ");
Messenger.m(currentRequester, String.format("gi Average tick time: %.3fms", divider * total_tick_time));
Messenger.m(currentRequester, "w ");
Messenger.m(currentRequester, "wb Average tick time: ", String.format("yb %.3fms", divider * total_tick_time));
SECTION_STATS.remove("tick");
Messenger.m(currentRequester, "gi Top 10 counts:");
Messenger.m(currentRequester, "wb Top 10 counts:");
int total = 0;
for (Object2LongMap.Entry<Pair<World, Object>> sectionEntry : ENTITY_COUNT.object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()))
{
if (++total > 10) break;
Pair<World,Object> section = sectionEntry.getKey();
boolean cli = section.getKey().isClient;
Messenger.m(currentRequester, String.format(
"gi - %s: %.1f",
sectionName(section),
1.0D * sectionEntry.getLongValue() / (tick_health_requested - (section.getKey().isClient? 1 : 0))
"%s - %s: ", cli?"gi":"w",
sectionName(section)),
String.format("%s %.1f", cli?"di":"y",
1.0D * sectionEntry.getLongValue() / (tick_health_requested - (cli? 1 : 0))
));
}
Messenger.m(currentRequester, "gi Top 10 CPU hogs:");
Messenger.m(currentRequester, "wb Top 10 CPU hogs:");
total = 0;
for (Object2LongMap.Entry<Pair<World,Object>> sectionEntry : ENTITY_TIMES.object2LongEntrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).collect(Collectors.toList()))
{
if (++total > 10) break;
Pair<World,Object> section = sectionEntry.getKey();
boolean cli = section.getKey().isClient;
Messenger.m(currentRequester, String.format(
"gi - %s: %.2fms",
sectionName(section),
(section.getKey().isClient ? divider : divider_1) * sectionEntry.getLongValue()
"%s - %s: ", cli?"gi":"w",
sectionName(section)),
String.format("%s %.2fms", cli?"di":"y",
(cli ? divider : divider_1) * sectionEntry.getLongValue()
));
}
}
@@ -1,4 +1,4 @@
__command() ->
_command() ->
(
print('camera: scarpet app.');
print('-------------------');
@@ -11,7 +11,7 @@ __command() ->
print(' "/camera place_player - Move player to the selected point');
print(' "/camera move" - Move the selected point to players location');
print(' "/camera duration <X>" - Set new selected path duration');
print(' "/camera split_point>" - Split selected path in half.');
print(' "/camera split_point" - Split selected path in half.');
print(' "/camera delete_point" - Remove current key point');
print(' "/camera trim_path" - Remove all key points from selected up');
print('');
@@ -57,6 +57,47 @@ __command() ->
print('');
null
);
__config() ->{
'commands'->{
''->'_command',
'<command>'->'_call',
'add <seconds>'->'add',
'prepend <seconds>'->'prepend',
'duration <seconds>' -> 'duration',
'save_as <name>'->'save_as',
'load <name>'->'load',
'interpolation <interpolation>'->['__interpolation',true],
'interpolation gauss'->['__interpolation','gauss',true],
'interpolation gauss <float>'->_(float)->(__interpolation('gauss_'+str(float),true)),
'repeat <seconds> <last_delay>'->'repeat',
'stretch <factor>'->'stretch'
},
'arguments'->{
'seconds'->{'type'->'int','suggest'->[]},
'last_delay'->{'type'->'int','suggest'->[]},
'name'->{'type'->'string','suggest'->[]},
'interpolation'->{'type'->'term','options'->['linear','catmull_rom']},
'factor'->{'type'->'int','min'->25,'max'->400},
'command'->{'type'->'term','options'->[
'start',
'clear',
'select',
'place_player',
'move',
'split_point',
'delete_point',
'trim_path',
'transpose',
'play',
'show',
'hide',
'prefer_smooth_play',
'prefer_synced_play'
]}
}
};
_call(command)->call(command);
global_points = null;
global_dimension = null;
@@ -150,7 +191,6 @@ __assert_point_selected(validator) ->
);
//select a custom interpolation method
interpolation(method) -> __interpolation(method, true);
__interpolation(method, verbose) ->
(
__prepare_path_if_needed() -> __prepare_path_if_needed_generic();
@@ -159,7 +199,7 @@ __interpolation(method, verbose) ->
// or optionally __prepare_path_if_needed, if path is inefficient to compute point by point
global_interpolator = if (
method == 'linear', '__interpolator_linear',
method == 'cr', '__interpolator_cr',
method == 'catmull_rom', '__interpolator_cr',
method == 'gauss', _(s, p) -> __interpolator_gauB(s, p, 0),
method ~ '^gauss_',
(
@@ -167,13 +207,12 @@ __interpolation(method, verbose) ->
type = replace(type,'_','.');
variance = round(60*number(type));
_(s, p, outer(variance)) -> __interpolator_gauB(s, p, variance);
),
exit('Choose one of the following methods: linear, gauss, gauss_<deviation>, cr')
)
);
__update();
if(verbose, 'Interpolation changed to '+method, '');
);
__interpolation('cr', false);
__interpolation('catmull_rom', false);
// adds a point to the end of the path with delay in seconds
add(delay) ->
@@ -216,9 +255,6 @@ repeat(times, last_section_delay) ->
stretch(percentage) ->
(
if (err = __is_not_valid_for_motion(), exit(err));
if (percentage < 25 || percentage > 400,
exit('path speed can only be speed, or slowed down 4 times. Re-call command for larger changes')
);
ratio = percentage/100;
previous_path_length = global_points:(-1):1;
for(global_points, _:1 = _:1*ratio );
@@ -660,4 +696,4 @@ __on_close() ->
(
global_playing_path = false;
global_showing_path = false;
);
);
@@ -1,21 +1,23 @@
import('math','_euclidean_sq','_vec_length');
import('shapes','draw_sphere', 'draw_diamond', 'draw_filled_diamond', 'draw_pyramid', 'draw_prism'); //importing all the shape funcs from the other app
__config() -> {
'commands'->{
'sphere <center> <radius> <block>'->['draw_sphere',null,true],
'sphere <center> <radius> <block> replace <replacement>'->['draw_sphere',true],
'ball <center> <radius> <block>'->['draw_sphere',null,false],
'ball <center> <radius> <block> replace <replacement>'->['draw_sphere',false],
'diamond <center> <radius> <block>'->['draw_diamond',null],
'diamond <center> <radius> <block> replace <replacement>'->'draw_diamond',
'pyramid <center> <radius> <height> <pointing> <orientation> <block> <hollow>'->['draw_pyramid',null, true],
'pyramid <center> <radius> <height> <pointing> <orientation> <block> <hollow> replace <replacement>'->['draw_pyramid', true],
'cone <center> <radius> <height> <pointing> <orientation> <block> <hollow>'->['draw_pyramid',null, false],
'cone <center> <radius> <height> <pointing> <orientation> <block> <hollow> replace <replacement>'->['draw_pyramid', false],
'cuboid <center> <radius> <height> <orientation> <block> <hollow>'->['draw_prism', null, true],
'cuboid <center> <radius> <height> <orientation> <block> <hollow> replace <replacement>'->['draw_prism', true],
'cylinder <center> <radius> <height> <orientation> <block> <hollow>'->['draw_prism', null, false],
'cylinder <center> <radius> <height> <orientation> <block> <hollow> replace <replacement>'->['draw_prism', false]
'sphere <center> <radius> <block>'->_(c,r,b)->draw('draw_sphere',[c,r, true],b,null),
'sphere <center> <radius> <block> replace <replacement>'->_(c,r,b,rp)->draw('draw_sphere',[c,r,true],b,rp),
'ball <center> <radius> <block>'->_(c,r,b)->draw('draw_sphere',[c,r,false],b,null),
'ball <center> <radius> <block> replace <replacement>'->_(c,r,b,rp)->draw('draw_sphere',[c,r,false],b,rp),
'diamond <center> <radius> <block> hollow' -> _(c,r,b)->draw('draw_diamond',[c,r],b,null),
'diamond <center> <radius> <block> hollow replace <replacement>'->_(c,r,b,rp)->draw('draw_diamond',[c, r], b, rp),
'diamond <center> <radius> <block>' -> _(c,r,b)->draw('draw_filled_diamond',[c,r],b,null),
'diamond <center> <radius> <block> replace <replacement>'->_(c,r,b,rp)->draw('draw_filled_diamond',[c, r], b, rp),
'pyramid <center> <radius> <height> <pointing> <orientation> <block> <hollow>'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,ho, true],b,null),
'pyramid <center> <radius> <height> <pointing> <orientation> <block> <hollow> replace <replacement>'->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,ho,true],b,rp),
'cone <center> <radius> <height> <pointing> <orientation> <block> <hollow>'->_(c,r,h,p,o,b,ho)->draw('draw_pyramid',[c,r,h,p,o,ho, false],b,null),
'cone <center> <radius> <height> <pointing> <orientation> <block> <hollow> replace <replacement>'->_(c,r,h,p,o,b,ho,rp)->draw('draw_pyramid', [c,r,h,p,o,ho,false],b,rp),
'cuboid <center> <radius> <height> <orientation> <block> <hollow>'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,ho, true],b,null),
'cuboid <center> <radius> <height> <orientation> <block> <hollow> replace <replacement>'->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,ho,true],b,rp),
'cylinder <center> <radius> <height> <orientation> <block> <hollow>'->_(c,r,h,o,b,ho)->draw('draw_prism',[c,r,h,p,o,ho, false],b,null),
'cylinder <center> <radius> <height> <orientation> <block> <hollow> replace <replacement>'->_(c,r,h,o,b,ho,rp)->draw('draw_prism', [c,r,h,o,ho,false],b,rp),
},
'arguments'->{
'center'->{'type'->'pos', 'loaded'->'true'},
@@ -26,12 +28,9 @@ __config() -> {
'pointing'->{'type'->'term','suggest'->['up','down']},
'hollow'->{'type'->'term','suggest'->['hollow','solid']},
},
'scope'->'global'
'scope'->'global'//todo decide whether it rly needs to be global or not...
};
//"Boilerplate" code
_block_matches(existing, block_predicate) ->
(
[name, block_tag, properties, nbt] = block_predicate;
@@ -42,112 +41,16 @@ _block_matches(existing, block_predicate) ->
(!tag || tag_matches(block_data(existing), tag))
);
set_block(x, y, z, block, replacement)-> (
existing = block(x, y, z);
if(block != existing && (replacement && _block_matches(existing, replacement) ),
without_updates( if (set(existing,block),
global_affected += [x, y, z];
));
draw(what, args, block, replacement)->(//custom setter cos it's easier
positions = call(what,args); //returning blocks to be set
affected = 0
for(positions,
existing = block(_);
if(block != existing && (!replacement || _block_matches(existing, replacement)),
affected += bool(set(existing,block))
)
);
);
global_affected = [];
affected(player) -> (
affected = length(global_affected);
print(player,format('gi Filled ' + affected + ' blocks'));
if(system_info('world_carpet_rules'):'fillUpdates',
for(global_affected,update(_));//updating if fillUpdates is true
);
global_affected = [];
affected
);
length_sq(vec) -> reduce(vec, _a + _*_, 0);
fill_flat(pos, offset, dr, rectangle, orientation, block, hollow, replacement)->(
r = floor(dr);
drsq = dr^2;
if(orientation=='x',
scan(pos,0,-r,-r,
if((!hollow && (rectangle || _y^2 + _z^2 <= drsq))||//if not hollow, vry simple
(hollow && ((rectangle && (abs(_y) == r || abs(_z) ==r)) || //If hollow and it's a rectangle
(!rectangle && (_y^2 + _z^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle
set_block(_x+offset,_y,_z,block, replacement)
)
),
orientation == 'y',
scan(pos,-r,0,-r,
if((!hollow && (rectangle || _x^2 + _z^2 <= drsq))||//if not hollow, vry simple
(hollow && ((rectangle && (abs(_x) == r || abs(_z) ==r)) || //If hollow and it's a rectangle
(!rectangle && (_x^2 + _z^2 <= drsq && (abs(_x)+1)^ 2 + (abs(_z)+1)^2 >= drsq)))),//If hollow and not rectangle
set_block(_x,_y+offset,_z,block, replacement)
)
),
orientation == 'z',
scan(pos,-r,-r,0,
if((!hollow && (rectangle || _y^2 + _x^2 <= drsq))||//if not hollow, vry simple
(hollow && ((rectangle && (abs(_y) == r || abs(_x) ==r)) || //If hollow and it's a rectangle
(!rectangle && (_y^2 + _x^2 <= drsq && (abs(_y)+1)^ 2 + (abs(_x)+1)^2 >= drsq)))),//If hollow and not rectangle
set_block(_x,_y,_z+offset,block, replacement)
)
),
print(player(),format('r Error while running command: orientation can only be "x", "y" or "z", '+orientation+' is invalid.'));
global_affected = [];
);
);
//drawing commands
draw_sphere(centre, radius, block, replacement, hollow)->(
scan(centre,[radius,radius,radius],
l = length_sq([_x,_y,_z]-centre);
if((l<=radius^2+radius) && (!hollow || l>=radius^2-radius),
set_block(_x, _y, _z, block, replacement)
)
);
affected(player());
);
draw_diamond(pos, radius, block, replacement)->(
c_for(r=0, r<radius, r+=1,
y = r-radius+1;
c_for(x=-r,x<=r,x+=1,
z=r-abs(x);
set_block(pos:0+x,pos:1-y,pos:2+z, block, replacement);
set_block(pos:0+x,pos:1-y,pos:2-z, block, replacement);
set_block(pos:0+x,pos:1+y,pos:2+z, block, replacement);
set_block(pos:0+x,pos:1+y,pos:2-z, block, replacement);
)
);
affected(player())
);
draw_pyramid(pos, radius, height, pointing, orientation, block, fill_type, replacement, is_square)->(
hollow = fill_type=='hollow';
pointup = pointing=='up';
for(range(height),
r = if(pointup, radius - radius * _ / height -1, radius * _ / height);
fill_flat(pos, _, r, is_square, orientation, block, if((pointup&&_==0)||(!pointup && _==height-1),false,hollow),replacement)//Always close bottom off
);
affected(player())
);
draw_prism(pos, rad, height, orientation, block, fill_type, replacement, is_square)->(
hollow = fill_type =='hollow';
radius = rad+0.5;
for(range(height),
fill_flat(pos, _, radius, is_square, orientation, block, if(_==0 || _==height-1,false,hollow), replacement)//Always close ends off
);
affected(player())
);
@@ -328,3 +328,32 @@ __on_close() ->
print('App is closing for '+player());
logger('App is closing for '+player());
);
__on_explosion(pos, power, source, causer, mode, fire) ->
(
print('');
print('__on_explosion(pos, power, source, causer, mode, fire)');
print('explosion:');
print(' - pos: '+pos);
print(' - power: '+power);
print(' - source: '+source);
print(' - causer: '+causer);
print(' - mode: '+mode);
print(' - fire: '+fire);
);
__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities) ->
(
print('');
print('__on_explosion_outcome(pos, power, source, causer, mode, fire, blocks, entities)');
print('explosion outcome:');
print(' - pos: '+pos);
print(' - power: '+power);
print(' - source: '+source);
print(' - causer: '+causer);
print(' - mode: '+mode);
print(' - fire: '+fire);
print(' - number or affected blocks: '+length(blocks));
print(' - number of affected entities: '+length(entities));
);
@@ -0,0 +1,233 @@
//This is a library app, mostly for the draw command, which allows to import these shape-drawing functions into your own app.
//Arguments for each are to be given as a singleton list, for ease of use. This is with the exception of the fill_flat functions, with
//the assumption that they're unlikely to be used outside of internal calls, but it is still available to be called if necessary.
//Make sure you read the arguments and input them in the correct order, or it will mess up.
//these functions return a set of all the positions in the shape, given the parameters, without repeating a position.
//This way, you can efficiently iterate over the positions once having generated them. See draw_beta.sc for an example. todo change to draw once renamed
draw_sphere(args)->(
[centre, radius, hollow] = args;
positions = {};
[cx,cy,cz]=centre;
for(range(-90, 90, 45/radius),
cpitch = cos(_);
spitch = sin(_);
for(range(0, 180, 45/radius),
cyaw = cos(_)*cpitch*radius;
syaw = sin(_)*cpitch*radius;
if(hollow,
positions += [cx+cyaw,cy+spitch*radius,cz+syaw];
positions += [cx+cos(_+180)*cpitch*radius,cy+spitch*radius,cz+sin(_+180)*cpitch*radius],
for(range(-syaw,syaw+1),
positions += [cx+cyaw*cpitch,cy+spitch*radius,cz+_]
)
)
)
);
positions
);
draw_diamond(args)->(
[pos, radius] = args;
positions = {};
c_for(r=0, r<radius, r+=1,
y = r-radius+1;
c_for(x=-r,x<=r,x+=1,
z=r-abs(x);
positions += [pos:0+x, pos:1 +y, pos:2+z];
positions += [pos:0+x, pos:1 +y, pos:2-z];
positions += [pos:0+x, pos:1 -y, pos:2+z];
positions += [pos:0+x, pos:1 -y, pos:2-z];
)
);
positions
);
draw_filled_diamond(args)->(
[pos, radius] = args;
positions = {};
for(diamond(pos,radius,radius),
positions += pos(_)
);
positions
);
draw_pyramid(args)->(
[pos, radius, height, pointing, orientation, fill_type, is_square] = args;//todo change to hollow boolean arg
positions = {};
hollow = fill_type=='hollow';
pointup = pointing=='up';
for(range(height),
r = if(pointup, radius * ( 1- _ / height) -1, radius * _ / height);
positions += fill_flat(pos, _, r, is_square, orientation, !((pointup&&_==0)||(!pointup && _==height-1)) && hollow)//Always close bottom off
);
positions
);
draw_prism(args)->(
[pos, rad, height, orientation, fill_type, is_square]=args;//todo change to hollow boolean arg
positions = {};
hollow = fill_type =='hollow';
radius = rad+0.5;
filled_circle_points = fill_flat(pos, 0, radius, is_square, orientation, false); //I dont actually need to generate all the points lol
offset_vector = if(orientation=='x', [1,0,0], orientation=='y', [0,1,0],[0,0,1]);
if(hollow,
hollow_circle_points = fill_flat(pos, 0, radius, is_square, orientation, true);
for(filled_circle_points,//Always close ends off
positions+=_;
positions+=_+offset_vector*(height-1)
);
for(range(1, height-1),//cos Im adding ends as filled anyways
offset=_;
for(hollow_circle_points,
positions+= _ + offset_vector * offset
)
),
for(range(height),
offset = _;
for(filled_circle_points,
positions += _ + offset_vector * offset
)
);
)
positions
);
fill_flat(pos, offset, dr, rectangle, orientation, hollow)->
if(rectangle,
fill_flat_rectangle(pos, offset, dr, orientation, hollow),
fill_flat_circle(pos, offset, dr, orientation, hollow)
);
fill_flat_circle(pos, offset, dr, orientation, hollow) ->( //Bresenham circle algorithm to be super efficient
r = floor(dr);
a=0;
b=r;
d=3-(2*r);
positions = {};
positions += fill_flat_circle_points(pos, orientation, offset, hollow, a, b);
while(a<=b, 10^10,
if(d<=0,
d += (4*a)+6,
d += (4*a)-(4*b)+10;
b=b-1
);
a+=1;
positions += fill_flat_circle_points(pos, orientation, offset, hollow, a, b)
);
positions
);
fill_flat_circle_points(pos, orientation, offset, hollow, a, b) ->(
[x, y, z] = pos;
positions = {};
if(orientation == 'x',
if(hollow,
positions += [x + offset, a+y, b+z];
positions += [x + offset, a+y,-b+z];
positions += [x + offset,-a+y,-b+z];
positions += [x + offset,-a+y, b+z];
positions += [x + offset, b+y, a+z];
positions += [x + offset, b+y,-a+z];
positions += [x + offset,-b+y,-a+z];
positions += [x + offset,-b+y, a+z],
c_for(c = -a, c<=a, c+=1,
positions += [x + offset,c+y, b + z];
positions += [x + offset,c+y,-b + z]
);
c_for(c = -b, c<=b, c+=1,
positions += [x + offset, a+y,c + z];
positions += [x + offset,-a+y,c + z]
)
),
orientation=='y',
if(hollow,
positions += [ a+x, y + offset, b+z];
positions += [ a+x, y + offset,-b+z];
positions += [-a+x, y + offset,-b+z];
positions += [-a+x, y + offset, b+z];
positions += [ b+x, y + offset, a+z];
positions += [ b+x, y + offset,-a+z];
positions += [-b+x, y + offset,-a+z];
positions += [-b+x, y + offset, a+z],
c_for(c = -a, c<=a, c+=1,
positions += [c+x,y + offset, b + z];
positions += [c+x,y + offset,-b + z]
);
c_for(c = -b, c<=b, c+=1,
positions += [ a+x,y + offset,c + z];
positions += [-a+x,y + offset,c + z]
)
),
orientation=='z',
if(hollow,
positions += [ a+x, b+y, z + offset];
positions += [ a+x,-b+y, z + offset];
positions += [-a+x,-b+y, z + offset];
positions += [-a+x, b+y, z + offset];
positions += [ b+x, a+y, z + offset];
positions += [ b+x,-a+y, z + offset];
positions += [-b+x,-a+y, z + offset];
positions += [-b+x, a+y, z + offset],
c_for(c = -a, c<=a, c+=1,
positions += [c+x, b + y,z + offset];
positions += [c+x,-b + y,z + offset]
);
c_for(c = -b, c<=b, c+=1,
positions += [ a+x,c + y,z + offset];
positions += [-a+x,c + y,z + offset]
)
)
)
);
fill_flat_rectangle(pos, offset, dr, orientation, hollow) -> (
r = floor(dr);
drsq = dr^2;
positions = {};
if(orientation == 'x',
c_for(a = -r, a <=r, a +=1,
if(hollow,
positions += [r + offset, a, r];
positions += [r + offset, a,-r];
positions += [r + offset, r, a];
positions += [r + offset,-r, a],
c_for(b = -r, b <=r, b +=1,
positions += [r + offset, a, b]
)
)
),
orientation == 'y',
c_for(a = -r, a <=r, a +=1,
if(hollow,
positions += [ r, r + offset,a];
positions += [-r, r + offset,a];
positions += [a, r + offset, r];
positions += [a, r + offset,-r],
c_for(b = -r, b <=r, b +=1,
positions += [a, r + offset, b]
)
)
),
orientation == 'z',
c_for(a = -r, a <=r, a +=1,
if(hollow,
positions += [ r, a,r + offset];
positions += [-r, a,r + offset];
positions += [a, r, r + offset];
positions += [a,-r, r + offset],
c_for(b = -r, b <=r, b +=1,
positions += [b, a, r + offset]
)
)
),
);
positions
);
+4
View File
@@ -24,6 +24,7 @@
"ServerPlayNetworkHandler_interactionUpdatesMixin",
"ServerMetadata_motdMixin",
"MinecraftServer_pingPlayerSampleLimit",
"MinecraftServer_coreMixin",
@@ -128,6 +129,7 @@
"TntBlockMixin",
"ExplosionMixin",
"ExplosionAccessor",
"Explosion_scarpetEventMixin",
"TntEntityMixin",
"ProjectileEntityMixin",
@@ -175,6 +177,7 @@
"PointOfInterest_scarpetMixin",
"ServerWorld_scarpetMixin",
"HungerManagerMixin_scarpetEventsMixin",
"ReloadCommand_reloadAppsMixin",
"RecipeManager_scarpetMixin",
"Ingredient_scarpetMixin",
@@ -192,6 +195,7 @@
"PlayerEntity_creativeNoClipMixin",
"PistonBlockEntity_playerHandlingMixin",
"BlockItem_creativeNoClipMixin",
"WallStandingBlockItem_creativeNoClipMixin",
"LivingEntity_creativeFlyMixin"
],