Merge branch 'master' into scoreboard-criterion-mod

This commit is contained in:
replaceitem authored and GitHub committed 2020-12-16 20:40:29 +01:00
commit 6bb7eb2ae2
63 files changed
+2386 -797

No files matched your search

+6 -1
View File
@@ -6,6 +6,10 @@ out/
classes/ classes/
publish/ publish/
# git
.shelf/
# idea # idea
.idea/ .idea/
@@ -27,4 +31,5 @@ bin/
# fabric # fabric
run/ run/
logs/
+347 -165
View File
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
## Code delivery, line indicators ## Code delivery, line indicators
Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing
your code in apps (files with `.sc` extension that can be placed inside "/scripts" folder in the world files and your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files
loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded
contain code, no need to start with "/script run" prefix as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only
contain code, no need to start with `/script run` prefix.
The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder
@@ -803,6 +804,9 @@ a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5)
=> Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter. => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
</pre> </pre>
In case you want to create a task based on a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` ### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)`
@@ -1372,7 +1376,7 @@ foo(... x) -> ... # all arguments for foo are included in the list
### `import(module_name, symbols ...)` ### `import(module_name, symbols ...)`
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
them in the current app. This include other symbols imported by these modules. Scarpet supports cicular dependencies, them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies,
but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them.
Returns full list of available symbols that could be imported from this module, which can be used to debug import Returns full list of available symbols that could be imported from this module, which can be used to debug import
issues, and list contents of libraries. issues, and list contents of libraries.
@@ -1381,9 +1385,57 @@ issues, and list contents of libraries.
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
except you can use it with function value, or name instead. This means you can pass functions to other user defined except you can use it with function value, or name instead. This means you can pass functions to other user defined
functions as arguments and call them with `call` internally. And since function definitions return the defined functions as arguments and call them with `call` internally. Since function definitions return the defined
function, they can be defined in place as anonymous functions. function, they can be defined in place as anonymous functions.
#### Passing function references to other modules of your application
In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports,
meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the
library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app,
it should import back that method from the main module for visibility.
Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in
the parent app:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme('foo' , 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just
a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like
imports in Java classes), and that solves the issue but makes the library dependent on the main app:
```
//lib.scl
import('app','foo');
callme(fun, arg) -> call(fun, arg);
```
You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created
in the module in which the requested function is visible:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme(_(x) -> foo(x), 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
Or by passing an explicit reference to the function, instead of calling it by name:
```
//app.sc
import('lib', 'callme');
global_foohandler = (foo(x) -> x*x);
test() -> callme(global_foohandler, 5);
```
Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not
creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
programmers with their own lambda arguments programmers with their own lambda arguments
@@ -1658,6 +1710,8 @@ map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(range(10),_a+_, 0) => 45 reduce(range(10),_a+_, 0) => 45
range(5,10) => [5, 6, 7, 8, 9] range(5,10) => [5, 6, 7, 8, 9]
range(20, 10, -2) => [20, 18, 16, 14, 12] range(20, 10, -2) => [20, 18, 16, 14, 12]
range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2]
range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2]
</pre> </pre>
## Map operations ## Map operations
@@ -1705,7 +1759,9 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript
## App structure ## App structure
The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts` The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts`
folder. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope folder, flat. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world,
and here you can actually organize them in folders.
When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope
(more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the
`__config()` function, if present, which will be executed once. Loading the app will also bind specific `__config()` function, if present, which will be executed once. Loading the app will also bind specific
events to the event system (check Events section for details). events to the event system (check Events section for details).
@@ -1731,15 +1787,14 @@ the app state and all players interactions run in the same context, sharing defi
explicitly keyed with players, player names, uuids, etc. explicitly keyed with players, player names, uuids, etc.
Even for `'player'` scoped apps, you can access specific player app with with commandblocks using Even for `'player'` scoped apps, you can access specific player app with with commandblocks using
`/execute as <player> run script in <app> run ...`. `/execute as <player> run script in <app> run ...`.
To access global/server state for a player app, you need to disown the command from any player, To access global/server state for a player app, which you shouldn't do, you need to disown the command from any player,
so either use a command block, or any so either use a command block, or any
arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however
running anything in the global scope for a `'player'` scoped app is not recommended. running anything in the global scope for a `'player'` scoped app is not intended.
* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will * `'stay_loaded'`: defaults to `true`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will
stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down.
This is to allow to store multiple apps on the server/world and selectively decide which one should be running at WARNING: all apps will run once at startup anyways, so be aware that their actions that are called
startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded.
statically, will be performed once anyways.
* `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function, * `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function,
all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type
will be inferred from their names, otherwise will be inferred from their names, otherwise
@@ -1749,9 +1804,20 @@ conflicts and ambiguities between different paths of execution. While ambiguous
and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet
will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and
`true`, then scarpet will load all provided commands regardless. `true`, then scarpet will load all provided commands regardless.
* `'command_permission'` - indicates a custom permission to run the command. It can either be a number indicating
permission level (from 1 to 4) or a string value, one of: `'all'` (default), `'ops'` (default opped player with permission level of 2),
`'server'` - command accessible only through the server console and commandblocks, but not in chat, `'players'` - opposite
of the former, allowing only use in player chat. It can also be a function (lambda function or function value, not function name)
that takes 1 parameter, which represents the calling player, or `'null'` if the command represents a server call.
The function will prevent the command from running if it evaluates to `false`.
Please note, that Minecraft evaluates eligible commands for players when they join, or on reload/restart, so if you use a
predicate that is volatile and might change, the command might falsely do or do not indicate that it is available to the player,
however player will always be able to type it in and either succeed, or fail, based on their current permissions.
Custom permission applies to legacy commands with `'legacy_command_type_support'` as well
as for the custom commands defined with `'commands'`, see below.
* `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well * `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well
as for the custom commands defined with `'commands'`, see below as for the custom commands defined with `'commands'`, see below.
* `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below * `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below.
## Custom app commands ## Custom app commands
@@ -1904,6 +1970,20 @@ Here is a list of built-in types, with their return value formats, as well as a
* `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position * `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position
to be loaded. to be loaded.
* `'block'`: a valid block state wrapped in a block value (including block properties and data) * `'block'`: a valid block state wrapped in a block value (including block properties and data)
* `'blockpredicate`': returns a 4-tuple indicating conditions of a block to match: block name, block tag,
map of required state properties, and tag to match. Either block name or block tag are `null` but not both.
Property map is always specified, but its empty for no conditions, and matching nbt tag can be `null` indicating
no requirements. Technically the 'all-matching' predicate would be `[null, null, {}, null]`, but
block name or block tag is always specified. One can use the following routine to match a block agains this predicate:
```
block_to_match = block(x,y,z);
[block_name, block_tag, properties, nbt_tag] = block_predicate;
(block_name == null || block_name == block_to_match) &&
(block_tag == null || block_tags(block_to_match, block_tag)) &&
all(properties, block_state(block_to_match, _) == properties:_) &&
(!tag || tag_matches(block_data(block_to_match), tag))
```
* `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors. * `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors.
* `'columnpos'`: a pair of x and z coordinates. * `'columnpos'`: a pair of x and z coordinates.
* `'dimension'`: string representing a valid dimension in the world. * `'dimension'`: string representing a valid dimension in the world.
@@ -1992,18 +2072,19 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block`
All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the
functions referenced below refer to either method of passing block position. functions referenced below refer to either method of passing block position.
### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` ### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)`
First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value.
Second part, `block` is either block value as a result of `block()` function string value indicating the block name, Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name
and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to with optional state and block data. It is then followed by an optional
`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to
be set for the target block. be set for the target block.
If `block` is specified only by name, then if a If `block` is specified only by name, then if a
destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
properties. properties 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 The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
<pre> <pre>
set(0,5,0,'bedrock') => bedrock set(0,5,0,'bedrock') => bedrock
@@ -2019,11 +2100,15 @@ scan(0,100,0,20,20,20,set(_,block('glass')))
b = block('glass'); scan(0,100,0,20,20,20,set(_,b)) b = block('glass'); scan(0,100,0,20,20,20,set(_,b))
// yet another option, skips all parsing // yet another option, skips all parsing
set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor
set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - no parsing of properties
set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor
set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor
set(x,y,z,'iron_trapdoor', ['half','top']) // same
set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same
set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state
set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data
set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
</pre> </pre>
### `without_updates(expr)` ### `without_updates(expr)`
@@ -2168,6 +2253,16 @@ 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 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. that block, a block doesn't get destroyed either.
### `weather()`,`weather(type)`,`weather(type, ticks)`
If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather.
With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type.
NB: It can thunder without there being a thunderstorm, there has to be both rain and thunder to form a storm.
With two args, sets the weather to `type` for `ticks` ticks.
## Block and World querying ## Block and World querying
### `pos(block), pos(entity)` ### `pos(block), pos(entity)`
@@ -2738,14 +2833,16 @@ the future.
These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`.
### `scan(center, range, lower_range?, expr)` ### `scan(center, range, upper_range?, expr)`
Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions
by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in
positive values. positive values, so you can use that if you know the lower coord, and dimension by calling `'scan(center, 0, 0, 0, w, h, d, ...)`.
`center` can be defined either as three coordinates, a list of three coords, or a block value.
`range` and `lower_range` can have the same representations, just if its a block, it computes the distance to the center `center` can be defined either as three coordinates, a single tuple of three coords, or a block value.
`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center
as range instead of taking the values as is. as range instead of taking the values as is.
`expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself. `expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself.
Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context, Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context,
@@ -2770,7 +2867,7 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot
for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
</pre> </pre>
### `rect(centre, range?, positive_range?)` ### `rect(centre, range?, upper_range?)`
Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center
point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective
@@ -2778,7 +2875,7 @@ number of blocks in each direction. If `positive_range` arguments are specified,
it uses `range` for negative offset, and `positive_range` for positive. it uses `range` for negative offset, and `positive_range` for positive.
`centre` can be defined either as three coordinates, a list of three coords, or a block value. `centre` can be defined either as three coordinates, a list of three coords, or a block value.
`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center `range` and `positive_range` can have the same representations, just if they are block values, it computes the distance to the center
as range instead of taking the values as is.` as range instead of taking the values as is.`
### `diamond(centre_pos, radius?, height?)` ### `diamond(centre_pos, radius?, height?)`
@@ -2840,7 +2937,9 @@ to the `regular` group.
* `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of * `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of
living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that
category. category.
* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: * All entity tags including those provided with datapacks. Built-in entity tags include: `skeletons`, `raiders`,
`beehive_inhabitors` (bee, duh), `arrows` and `impact_projectiles`.
* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command:
`area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`, `area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`,
`chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`, `chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`,
`egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`, `egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`,
@@ -2862,10 +2961,13 @@ belonging to that group.
Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from
the center point. Uses the same `type` selectors as `entities_list`. the center point/area. Uses the same `type` selectors as `entities_list`.
`center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can `center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can
also be represented as a block, in this case the search box will be centered on the middle of the block. also be represented as a block, in this case the search box will be centered on the middle of the block, or an entity - in this case
entire bounding box of the entity serves as a 'center' of search which is then expanded in all directions with the `'distance'` vector.
In any case - returns all entities which bounding box collides with the bounding box defined by `'center'` and `'disteance'`.
entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and
cuboid search area. cuboid search area.
@@ -2994,13 +3096,21 @@ List of entities riding the entity.
Entity that `e` rides. Entity that `e` rides.
### `query(e, 'tags')` ### `query(e, 'scoreboard_tags')`, `query(e, 'tags')`(deprecated)
List of entity's tags. List of entity's scoreboard tags.
### `query(e, 'has_tag',tag)` ### `query(e, 'has_scoreboard_tag',tag)`, `query(e, 'has_tag',tag)`(deprecated)
Boolean, true if the entity is marked with `tag`. Boolean, true if the entity is marked with a `tag` scoreboad tag.
### `query(e, 'entity_tags')`
List of entity tags assigned to the type this entity represents.
### `query(e, 'has_entity_tag', tag)`
Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid.
### `query(e, 'is_burning')` ### `query(e, 'is_burning')`
@@ -3154,6 +3264,11 @@ Player's ping in milliseconds, or `null` if its not a player.
Player's permission level, or `null` if not applicable for this entity. Player's permission level, or `null` if not applicable for this entity.
### `query(e, 'client_brand')`
Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet <version>'` where
version indicates the version of the connected carpet client.
### `query(e, 'effect', name?)` ### `query(e, 'effect', name?)`
Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short
@@ -3176,6 +3291,18 @@ Number indicating remaining entity health, or `null` if not applicable.
Retrieves player hunger related information. For non-players, returns `null`. Retrieves player hunger related information. For non-players, returns `null`.
### `query(e, 'absorption')`
Gets the absorption of the player (yellow hearts, e.g when having a golden apple.)
### `query(e,'xp')`
### `query(e,'xp_level')`
### `query(e,'xp_progress')`
### `query(e,'score')`
Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar,
`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death
### `query(e, 'air')` ### `query(e, 'air')`
Number indicating remaining entity health, or `null` if not applicable. Number indicating remaining entity health, or `null` if not applicable.
@@ -3486,6 +3613,20 @@ Will set entity on fire for `ticks` ticks. Set to 0 to extinguish.
Modifies directly player raw hunger components. Has no effect on non-players Modifies directly player raw hunger components. Has no effect on non-players
### `modify(e, 'absorption', value)`
Sets the absorption value for the player. Each point is half a yellow heart.
### `modify(e, 'add_xp', value)`
### `modify(e, 'xp_level', value)`
### `modify(e, 'xp_progress', value)`
### `modify(e, 'xp_score', value)`
Manipulates player xp values - `'add_xp'` the method you probably want to use
to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and
`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value,
maybe you will get a double, who knows.
### `modify(e, 'air', ticks)` ### `modify(e, 'air', ticks)`
Modifies entity air Modifies entity air
@@ -3529,6 +3670,10 @@ Required arguments: `entity, amount, source, attacking_entity`
It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error
when you attach an inapplicable event to it. when you attach an inapplicable event to it.
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
@@ -3627,6 +3772,14 @@ 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. 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?)
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?)
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
### `stack_limit(item)` ### `stack_limit(item)`
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
@@ -3800,10 +3953,8 @@ with `__on_<event>` and has the required arguments, will be bound automatically
of such function would result in unbinding the app from this event. of such function would result in unbinding the app from this event.
In case of `player` scoped apps, In case of `player` scoped apps,
all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, cannot be handled all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
in `'player'` scoped app. player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
`'global'` scoped apps will receive both global and player built-in events.
Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice
for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the
@@ -3822,8 +3973,15 @@ if it accepts required number of parameters.
## Built-in global events ## Built-in global events
Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
simply wouldn't know which player to hook up this event to. is responsible independently from handling their events, so a global event may be executed multiple times for each player.
### `__on_server_starts()`
Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`.
### `__on_server_shuts_down()`
Event triggers when the server started the shutdown process, before `__on_close()` is executed. Unlike `__on_close()`, it doesn't
trigger with `/reload`.
### `__on_tick()` ### `__on_tick()`
Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()`
@@ -3900,7 +4058,12 @@ adjusted.
### `__on_player_interacts_with_entity(player, entity, hand)` ### `__on_player_interacts_with_entity(player, entity, hand)`
Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or
the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side
with that interaction with that interaction.
### `__on_player_trades(player, entity, buy_left, buy_right, sell)`
Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory
changes and merchant updates its trade-uses counter.
The parameter `entity` can be `null` if the merchant is not an entity.
### `__on_player_collides_with_entity(player, entity)` ### `__on_player_collides_with_entity(player, entity)`
Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game. Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game.
@@ -3996,8 +4159,11 @@ is handled before scoreboard values for these statistics are changed.
App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value
as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is
either targetting all global context apps, if no target player has been identified, or only player scoped apps, if the target player either targetting all apps instances for all players, including global apps, if no target player has been identified,
is specified. or only player scoped apps, if the target player
is specified, running once for that player app. You cannot target global apps with player-targeted signals. Built-in events
do target global apps, since their first argument is clearly defined and passed. That may change in the future in case there is
a compelling argument to be able to target global apps with player scopes.
Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have
have no idea why you would need that. The following snippets have the same effect: have no idea why you would need that. The following snippets have the same effect:
@@ -4021,11 +4187,11 @@ handle_event('player_breaks_block', null);
And `signal_event` can be used as a trigger, called twice for player based built-in events And `signal_event` can be used as a trigger, called twice for player based built-in events
``` ```
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
``` ```
or (for global only events) or (for global events)
``` ```
signal_event('tick') // trigger only global scoped apps with no extra arguments signal_event('tick') // trigger all apps with a tick event
``` ```
### `handle_event(event, callback ...)` ### `handle_event(event, callback ...)`
@@ -4039,6 +4205,8 @@ If extra arguments are provided, they will be appended to the argument list of t
Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event, Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event,
or incorect number of parameters for the event). or incorect number of parameters for the event).
If a callback is specified as `null`, the given app (or player app instance )stops handling that event.
<pre> <pre>
foo(a) -> print(a); foo(a) -> print(a);
handle_event('boohoo', 'foo'); handle_event('boohoo', 'foo');
@@ -4051,116 +4219,64 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event
handle_event('tick', null) // nah, ima good, kthxbai handle_event('tick', null) // nah, ima good, kthxbai
</pre> </pre>
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `signal_event(event, target_player?, ... args?)` ### `signal_event(event, target_player?, ... args?)`
Fires a specific event. If the event does not exist (only `handle_event` creates missing mising new events), or provided argument list Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
was not matching the callee expected arguments, returns `null`, was not matching the callee expected arguments, returns `null`,
otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting
only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event. only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event.
If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped. If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
all app scopes. Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function. provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function.
<pre> <pre>
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
signal_event('tick') // triggering tick event in all loaded apps signal_event('tick') // trigger all apps with a tick event
</pre> </pre>
## Custom events example ## Custom events example
The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous The following example shows how you can communicate between different instances of the same player scoped app. It important to note
squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called
in this case the event is once called as a global event, and once as a player event, targeting each app once. Custom events is `tp_request` and is triggered with a command.
a good idea to share common event detection code with one app, and using the outcome of these events in multiple places.
Obviously you would define each event handler once not twice in your app system.
In this
case here we have two ways of detecting all players squatting for more than 40 ticks - one of those apps will produce cloud particles
in an apprpriate location, and the other will produce a sound when it happens, and they both will proceed to signal the same event,
thankfully for different scopes, otherwise other apps would trigger twice.
``` ```
// detector1.sc // tpa.sc
// detecting event in a tick loop global_requester = null;
__config() -> {'scope' -> 'global'}; __config() -> {
'commands' -> {
global_sneaks = {}; '<player>' -> _(to) -> signal_event('tp_request', to, player()),
__on_tick() -> for(player('all'), 'accept' -> _() -> if(global_requester,
if (_~'sneaking', run('tp '+global_requester~'command_name');
sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0); global_requester = null
if (sneak_time > 20 && sneak_time < 40,
particle('cloud', pos(_) + [0, _~'height'/2, 0] - _~'look', 1, 0.1)
);
if (sneak_time == 40,
signal_event('player_farts', _, _) // triggering all player scoped apps from a global app
) )
, // else },
delete(global_sneaks:_) 'arguments' -> {
) 'player' -> {'type' -> 'players', 'single' -> true}
) }
};
handle_event('tp_request', _(req) -> (
global_requester = req;
print(player(), format(
'w '+req+' requested to teleport to you. Click ',
'yb here', '^yb here', '!/tpa accept',
'w to accept it.'
));
));
``` ```
```
// detector2.sc
// detecting events in a more appropriate way - player based and event based app
__on_player_starts_sneaking(p) ->
(
global_sneaking = tick_time();
schedule(40, 'check', p);
);
check(p) ->
if( global_sneaking && tick_time() - global_sneaking == 40,
sound('entity.shulker.shoot', pos(p), 1, 0.6, 'player');
signal_event('player_farts', null, p) // trigerring all global apps from a player scoped app
);
__on_player_stops_sneaking(p) -> (global_sneaking = false);
```
And then two other apps that meant to do something with this event. One mimics an explosion, and the other - grows crops around
the player.
```
client1.sc
__config() -> {'scope' -> 'global'};
on_fart(p, msg) ->
(
print(p+msg);
particle('explosion', pos(p));
sound('entity.generic.explode', pos(p), 1, 0.5);
);
handle_event('player_farts', 'on_fart', ' goes kaboom');
```
```
client2.sc
longsneak(p) ->
(
ppos = pos(p);
loop ( 500,
target = ppos+[rand(12),rand(8),rand(12)]-[rand(12),rand(8),rand(12)];
if (material(target) == 'plant' && ticks_randomly(target),
particle('happy_villager', target, 2, 0.4)
);
random_tick(target)
)
);
handle_event('player_farts', 'longsneak');
```
## `/script event` command ## `/script event` command
used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to used to display current events and bounded functions. use `add_to` to register a new event, or `remove_from` to
unbind a specific function from an event. Function to be bounded to an event needs to have the same number of unbind a specific function from an event. Function to be bounded to an event needs to have the same number of
parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load` parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load`
that have functions listed above will be automatically bounded and unbounded when script is unloaded. that handle specific built-in events will be automatically bounded, and unbounded when script is unloaded.
# Scoreboard # Scoreboard
### `scoreboard()`, `scoreboard(objective)`, `scoreboard(objective, key)`, `scoreboard(objective, key, value)` ### `scoreboard()`, `scoreboard(objective)`, `scoreboard(objective, key)`, `scoreboard(objective, key, value)`
@@ -4267,6 +4383,65 @@ team_property('admin','seeFriendlyInvisibles',true) Make all players in 'a
team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible
``` ```
## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)`
Manage bossbars just like with the `/bossbar` command.
Without any arguments, returns a list of all bossbars.
When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar.
Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`.
In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically.
If the id was invalid (for example by having more than one colon), returns `null`.
If the bossbar already exists, returns `false`.
`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified)
`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified
`bossbar('foo:bar:baz') => null` Invalid identifier
`bossbar(id,property)` is used to query the `property` of a bossbar.
`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`.
Available properties are:
* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'`
* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'`
* value: value of the bossbar progress
* max: maximum value of the bossbar progress, by default 100
* name: Text to display above the bossbar, supports formatted text
* visible: whether the bossbar is visible or not
* players: List of players that can see the bossbar
* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present)
* remove: remove this bossbar, no `value` required
```
bossbar('script:test','style','notched_12')
bossbar('script:test','value',74)
bossbar('script:test','name',format('rb Test')) -> Change text
bossbar('script:test','visible',false) -> removes visibility, but keeps players
bossbar('script:test','players',player('all')) -> Visible for all players
bossbar('script:test','players',player('Steve')) -> Visible for Steve only
bossbar('script:test','players',null) -> Invalid player, removing all players
bossbar('script:test','add_player',player('Alex')) -> Add Alex to the list of players that can see the bossbar
bossbar('script:test','remove') -> remove bossbar 'script:test'
for(bossbar(),bossbar(_,'remove')) -> remove all bossbars
```
# Auxiliary aspects # Auxiliary aspects
Collection of other methods that control smaller, yet still important aspects of the game Collection of other methods that control smaller, yet still important aspects of the game
@@ -4462,9 +4637,10 @@ Other value types will only be converted to tags (including NBT tags) if `force`
extra treatment when loading them back from NBT, but using `force` true will always produce output / never extra treatment when loading them back from NBT, but using `force` true will always produce output / never
produce an exception. produce an exception.
### `print(expr)` ### `print(expr)`, `print(player/player_list, expr)`
Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr.
Can optionally define player or list of players to send the message to.
### `format(components, ...)`, `format(l(components, ...))` ### `format(components, ...)`, `format(l(components, ...))`
@@ -4544,13 +4720,15 @@ Available output types:
### `read_file(resource, type)` ### `read_file(resource, type)`
### `delete_file(resource, type)` ### `delete_file(resource, type)`
### `write_file(resource, type, data, ...)` ### `write_file(resource, type, data, ...)`
### `list_files(resource, type)`
With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its
content, or deletes the resource. content, deletes the resource, or lists other files under this resource.
Resource is identified by a path to the file. Resource is identified by a path to the file.
A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred from the name. Empty descriptors are invalid, 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.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
@@ -4565,33 +4743,26 @@ 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. 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 and delete operations). Returns `true` Functions return `null` if an error is encounter or 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. for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files
for folder listing.
NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data.
Write operations to text files always result in appending to the existing file, so consecutive writes will increase
the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
* `write_file('temp', 'text', 'foo', 'bar', 'baz')` or
* write_file('temp', 'text', l('foo', 'bar', 'baz'))
To log a single line of string Supported values for resource `type` are:
Supported values for resource `type` is:
* `nbt` - NBT tag * `nbt` - NBT tag
* `text` - text resource with automatic newlines added * `text` - text resource with automatic newlines added
* `raw` - text resource without implied newlines * `raw` - text resource without implied newlines
* `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above * `folder` - for `list_files` only - indicting folder listing instead of files
* `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension,
stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be
sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each
record to the file. record to the file. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
<pre> <pre>
write_file('foo', 'shared_text, l('one', 'two')); write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n'); write_file('foo', 'shared_text', 'three\n', 'four\n');
write_file('foo', 'shared_raw', 'five\n', 'six\n'); write_file('foo', 'shared_raw', 'five\n', 'six\n');
@@ -4718,6 +4889,9 @@ algorithm has taken in to account last time mobs spawned.
Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end
of the tick, and they will run in order they were scheduled. of the tick, and they will run in order they were scheduled.
In case you want to schedule a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `statistic(player, category, entry)` ### `statistic(player, category, entry)`
Queries in-game statistics for certain values. Categories include: Queries in-game statistics for certain values. Categories include:
@@ -4757,7 +4931,9 @@ Available options in the scarpet app space:
* `world_path` - full path to the world saves folder * `world_path` - full path to the world saves folder
* `world_folder` - name of the direct folder in the saves that holds world files * `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_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 ...`.
Relevant gameplay related properties Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'` * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
* `game_hardcore` - boolean whether the game is in hardcore mode * `game_hardcore` - boolean whether the game is in hardcore mode
@@ -4769,13 +4945,13 @@ Available options in the scarpet app space:
* `game_version` - base version of the game * `game_version` - base version of the game
Server related properties Server related properties
* `server_motd` - the motd of the server visible when joining * `server_motd` - the motd of the server visible when joining
* `server_ip` - IP adress of the game hosted * `server_ip` - IP adress of the game hosted
* `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players
* `server_whitelist` - list of players allowed to log in * `server_whitelist` - list of players allowed to log in
* `server_banned_players` - list of banned player names * `server_banned_players` - list of banned player names
* `server_banned_ips` - list of banned IP addresses * `server_banned_ips` - list of banned IP addresses
* `server_dev_environment` - boolean indicating whether this server is in a development environment.
System related properties System related properties
* `java_max_memory` - maximum allowed memory accessible by JVM * `java_max_memory` - maximum allowed memory accessible by JVM
@@ -4785,7 +4961,11 @@ Available options in the scarpet app space:
* `java_version` - version of Java * `java_version` - version of Java
* `java_bits` - number indicating how many bits the Java has, 32 or 64 * `java_bits` - number indicating how many bits the Java has, 32 or 64
* `java_system_cpu_load` - current percentage of CPU used by the system * `java_system_cpu_load` - current percentage of CPU used by the system
* `java_process_cpu_load` - current percentage of CPU used by JVM# `/script run` command * `java_process_cpu_load` - current percentage of CPU used by JVM
Scarpet related properties
* `scarpet_version` - returns the version of the carpet your scarpet comes with.
# `/script run` command
Primary way to input commands. The command executes in the context, position, and dimension of the executing player, Primary way to input commands. The command executes in the context, position, and dimension of the executing player,
commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and
@@ -4795,13 +4975,15 @@ to simulate running commands in a different scope.
# `/script load / unload <app> (global?)`, `/script in <app>` commands # `/script load / unload <app> (global?)`, `/script in <app>` commands
`load / unload` commands allow for very conventient way of writing your code, providing it to the game and `load / unload` commands allow for very convenient way of writing your code, providing it to the game and
distribute with your worlds without the need of use of commandblocks. Just place your scarpet code in the distribute with your worlds without the need of use of commandblocks. Just place your Scarpet code in the
/scripts folder of your world files and make sure it ends with `.sc` extension. The good thing about editing that `/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can
code is that you can no only use normal editing without the need of marking of newlines, also save your scripts in `.minecraft/config/carpet/scripts` to make them available in any world.
The good thing about editing that code is that you can not only use normal editing without the need of marking of newlines,
but you can also use comments in your code. but you can also use comments in your code.
a comment is anything that starts with a double slash, and continues to the end of the line: A comment is anything that starts with a double slash, and continues to the end of the line:
<pre> <pre>
foo = 1; foo = 1;
+27 -23
View File
@@ -193,9 +193,10 @@ Other value types will only be converted to tags (including NBT tags) if `force`
extra treatment when loading them back from NBT, but using `force` true will always produce output / never extra treatment when loading them back from NBT, but using `force` true will always produce output / never
produce an exception. produce an exception.
### `print(expr)` ### `print(expr)`, `print(player/player_list, expr)`
Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr.
Can optionally define player or list of players to send the message to.
### `format(components, ...)`, `format(l(components, ...))` ### `format(components, ...)`, `format(l(components, ...))`
@@ -275,13 +276,15 @@ Available output types:
### `read_file(resource, type)` ### `read_file(resource, type)`
### `delete_file(resource, type)` ### `delete_file(resource, type)`
### `write_file(resource, type, data, ...)` ### `write_file(resource, type, data, ...)`
### `list_files(resource, type)`
With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its
content, or deletes the resource. content, deletes the resource, or lists other files under this resource.
Resource is identified by a path to the file. Resource is identified by a path to the file.
A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred from the name. Empty descriptors are invalid, 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.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
@@ -296,33 +299,26 @@ 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. 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 and delete operations). Returns `true` Functions return `null` if an error is encounter or 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. for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files
for folder listing.
NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data.
Write operations to text files always result in appending to the existing file, so consecutive writes will increase
the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
* `write_file('temp', 'text', 'foo', 'bar', 'baz')` or
* write_file('temp', 'text', l('foo', 'bar', 'baz'))
To log a single line of string Supported values for resource `type` are:
Supported values for resource `type` is:
* `nbt` - NBT tag * `nbt` - NBT tag
* `text` - text resource with automatic newlines added * `text` - text resource with automatic newlines added
* `raw` - text resource without implied newlines * `raw` - text resource without implied newlines
* `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above * `folder` - for `list_files` only - indicting folder listing instead of files
* `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension,
stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be
sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each
record to the file. record to the file. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
<pre> <pre>
write_file('foo', 'shared_text, l('one', 'two')); write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n'); write_file('foo', 'shared_text', 'three\n', 'four\n');
write_file('foo', 'shared_raw', 'five\n', 'six\n'); write_file('foo', 'shared_raw', 'five\n', 'six\n');
@@ -449,6 +445,9 @@ algorithm has taken in to account last time mobs spawned.
Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end
of the tick, and they will run in order they were scheduled. of the tick, and they will run in order they were scheduled.
In case you want to schedule a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `statistic(player, category, entry)` ### `statistic(player, category, entry)`
Queries in-game statistics for certain values. Categories include: Queries in-game statistics for certain values. Categories include:
@@ -488,7 +487,9 @@ Available options in the scarpet app space:
* `world_path` - full path to the world saves folder * `world_path` - full path to the world saves folder
* `world_folder` - name of the direct folder in the saves that holds world files * `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_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 ...`.
Relevant gameplay related properties Relevant gameplay related properties
* `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'` * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
* `game_hardcore` - boolean whether the game is in hardcore mode * `game_hardcore` - boolean whether the game is in hardcore mode
@@ -500,13 +501,13 @@ Available options in the scarpet app space:
* `game_version` - base version of the game * `game_version` - base version of the game
Server related properties Server related properties
* `server_motd` - the motd of the server visible when joining * `server_motd` - the motd of the server visible when joining
* `server_ip` - IP adress of the game hosted * `server_ip` - IP adress of the game hosted
* `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players
* `server_whitelist` - list of players allowed to log in * `server_whitelist` - list of players allowed to log in
* `server_banned_players` - list of banned player names * `server_banned_players` - list of banned player names
* `server_banned_ips` - list of banned IP addresses * `server_banned_ips` - list of banned IP addresses
* `server_dev_environment` - boolean indicating whether this server is in a development environment.
System related properties System related properties
* `java_max_memory` - maximum allowed memory accessible by JVM * `java_max_memory` - maximum allowed memory accessible by JVM
@@ -516,4 +517,7 @@ Available options in the scarpet app space:
* `java_version` - version of Java * `java_version` - version of Java
* `java_bits` - number indicating how many bits the Java has, 32 or 64 * `java_bits` - number indicating how many bits the Java has, 32 or 64
* `java_system_cpu_load` - current percentage of CPU used by the system * `java_system_cpu_load` - current percentage of CPU used by the system
* `java_process_cpu_load` - current percentage of CPU used by JVM * `java_process_cpu_load` - current percentage of CPU used by JVM
Scarpet related properties
* `scarpet_version` - returns the version of the carpet your scarpet comes with.
+8 -6
View File
@@ -2,14 +2,16 @@
These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`.
### `scan(center, range, lower_range?, expr)` ### `scan(center, range, upper_range?, expr)`
Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions
by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in
positive values. positive values, so you can use that if you know the lower coord, and dimension by calling `'scan(center, 0, 0, 0, w, h, d, ...)`.
`center` can be defined either as three coordinates, a list of three coords, or a block value.
`range` and `lower_range` can have the same representations, just if its a block, it computes the distance to the center `center` can be defined either as three coordinates, a single tuple of three coords, or a block value.
`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center
as range instead of taking the values as is. as range instead of taking the values as is.
`expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself. `expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself.
Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context, Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context,
@@ -34,7 +36,7 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot
for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
</pre> </pre>
### `rect(centre, range?, positive_range?)` ### `rect(centre, range?, upper_range?)`
Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center
point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective
@@ -42,7 +44,7 @@ number of blocks in each direction. If `positive_range` arguments are specified,
it uses `range` for negative offset, and `positive_range` for positive. it uses `range` for negative offset, and `positive_range` for positive.
`centre` can be defined either as three coordinates, a list of three coords, or a block value. `centre` can be defined either as three coordinates, a list of three coords, or a block value.
`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center `range` and `positive_range` can have the same representations, just if they are block values, it computes the distance to the center
as range instead of taking the values as is.` as range instead of taking the values as is.`
### `diamond(centre_pos, radius?, height?)` ### `diamond(centre_pos, radius?, height?)`
+25 -10
View File
@@ -36,18 +36,19 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block`
All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the
functions referenced below refer to either method of passing block position. functions referenced below refer to either method of passing block position.
### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` ### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)`
First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value.
Second part, `block` is either block value as a result of `block()` function string value indicating the block name, Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name
and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to with optional state and block data. It is then followed by an optional
`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to
be set for the target block. be set for the target block.
If `block` is specified only by name, then if a If `block` is specified only by name, then if a
destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
properties. properties 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 The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
<pre> <pre>
set(0,5,0,'bedrock') => bedrock set(0,5,0,'bedrock') => bedrock
@@ -63,11 +64,15 @@ scan(0,100,0,20,20,20,set(_,block('glass')))
b = block('glass'); scan(0,100,0,20,20,20,set(_,b)) b = block('glass'); scan(0,100,0,20,20,20,set(_,b))
// yet another option, skips all parsing // yet another option, skips all parsing
set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor set(x,y,z,'iron_trapdoor') // sets bottom iron trapdoor
set(x,y,z,'iron_trapdoor[half=top]') // Incorrect. sets bottom iron trapdoor - no parsing of properties
set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor
set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor
set(x,y,z,'iron_trapdoor', ['half','top']) // same
set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same
set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state
set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data
set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
</pre> </pre>
### `without_updates(expr)` ### `without_updates(expr)`
@@ -212,6 +217,16 @@ 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 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. that block, a block doesn't get destroyed either.
### `weather()`,`weather(type)`,`weather(type, ticks)`
If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will
always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather.
With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type.
NB: It can thunder without there being a thunderstorm, there has to be both rain and thunder to form a storm.
With two args, sets the weather to `type` for `ticks` ticks.
## Block and World querying ## Block and World querying
### `pos(block), pos(entity)` ### `pos(block), pos(entity)`
+55 -7
View File
@@ -52,7 +52,9 @@ to the `regular` group.
* `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of * `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of
living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that
category. category.
* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: * All entity tags including those provided with datapacks. Built-in entity tags include: `skeletons`, `raiders`,
`beehive_inhabitors` (bee, duh), `arrows` and `impact_projectiles`.
* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command:
`area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`, `area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`,
`chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`, `chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`,
`egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`, `egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`,
@@ -74,10 +76,13 @@ belonging to that group.
Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from
the center point. Uses the same `type` selectors as `entities_list`. the center point/area. Uses the same `type` selectors as `entities_list`.
`center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can `center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can
also be represented as a block, in this case the search box will be centered on the middle of the block. also be represented as a block, in this case the search box will be centered on the middle of the block, or an entity - in this case
entire bounding box of the entity serves as a 'center' of search which is then expanded in all directions with the `'distance'` vector.
In any case - returns all entities which bounding box collides with the bounding box defined by `'center'` and `'disteance'`.
entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and
cuboid search area. cuboid search area.
@@ -206,13 +211,21 @@ List of entities riding the entity.
Entity that `e` rides. Entity that `e` rides.
### `query(e, 'tags')` ### `query(e, 'scoreboard_tags')`, `query(e, 'tags')`(deprecated)
List of entity's tags. List of entity's scoreboard tags.
### `query(e, 'has_tag',tag)` ### `query(e, 'has_scoreboard_tag',tag)`, `query(e, 'has_tag',tag)`(deprecated)
Boolean, true if the entity is marked with `tag`. Boolean, true if the entity is marked with a `tag` scoreboad tag.
### `query(e, 'entity_tags')`
List of entity tags assigned to the type this entity represents.
### `query(e, 'has_entity_tag', tag)`
Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid.
### `query(e, 'is_burning')` ### `query(e, 'is_burning')`
@@ -366,6 +379,11 @@ Player's ping in milliseconds, or `null` if its not a player.
Player's permission level, or `null` if not applicable for this entity. Player's permission level, or `null` if not applicable for this entity.
### `query(e, 'client_brand')`
Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet <version>'` where
version indicates the version of the connected carpet client.
### `query(e, 'effect', name?)` ### `query(e, 'effect', name?)`
Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short
@@ -388,6 +406,18 @@ Number indicating remaining entity health, or `null` if not applicable.
Retrieves player hunger related information. For non-players, returns `null`. Retrieves player hunger related information. For non-players, returns `null`.
### `query(e, 'absorption')`
Gets the absorption of the player (yellow hearts, e.g when having a golden apple.)
### `query(e,'xp')`
### `query(e,'xp_level')`
### `query(e,'xp_progress')`
### `query(e,'score')`
Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar,
`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death
### `query(e, 'air')` ### `query(e, 'air')`
Number indicating remaining entity health, or `null` if not applicable. Number indicating remaining entity health, or `null` if not applicable.
@@ -698,6 +728,20 @@ Will set entity on fire for `ticks` ticks. Set to 0 to extinguish.
Modifies directly player raw hunger components. Has no effect on non-players Modifies directly player raw hunger components. Has no effect on non-players
### `modify(e, 'absorption', value)`
Sets the absorption value for the player. Each point is half a yellow heart.
### `modify(e, 'add_xp', value)`
### `modify(e, 'xp_level', value)`
### `modify(e, 'xp_progress', value)`
### `modify(e, 'xp_score', value)`
Manipulates player xp values - `'add_xp'` the method you probably want to use
to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and
`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value,
maybe you will get a double, who knows.
### `modify(e, 'air', ticks)` ### `modify(e, 'air', ticks)`
Modifies entity air Modifies entity air
@@ -741,6 +785,10 @@ Required arguments: `entity, amount, source, attacking_entity`
It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error
when you attach an inapplicable event to it. when you attach an inapplicable event to it.
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
+63 -100
View File
@@ -9,10 +9,8 @@ with `__on_<event>` and has the required arguments, will be bound automatically
of such function would result in unbinding the app from this event. of such function would result in unbinding the app from this event.
In case of `player` scoped apps, In case of `player` scoped apps,
all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, cannot be handled all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
in `'player'` scoped app. player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
`'global'` scoped apps will receive both global and player built-in events.
Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice
for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the
@@ -31,8 +29,15 @@ if it accepts required number of parameters.
## Built-in global events ## Built-in global events
Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
simply wouldn't know which player to hook up this event to. is responsible independently from handling their events, so a global event may be executed multiple times for each player.
### `__on_server_starts()`
Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`.
### `__on_server_shuts_down()`
Event triggers when the server started the shutdown process, before `__on_close()` is executed. Unlike `__on_close()`, it doesn't
trigger with `/reload`.
### `__on_tick()` ### `__on_tick()`
Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()`
@@ -109,7 +114,12 @@ adjusted.
### `__on_player_interacts_with_entity(player, entity, hand)` ### `__on_player_interacts_with_entity(player, entity, hand)`
Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or
the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side
with that interaction with that interaction.
### `__on_player_trades(player, entity, buy_left, buy_right, sell)`
Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory
changes and merchant updates its trade-uses counter.
The parameter `entity` can be `null` if the merchant is not an entity.
### `__on_player_collides_with_entity(player, entity)` ### `__on_player_collides_with_entity(player, entity)`
Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game. Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game.
@@ -205,8 +215,11 @@ is handled before scoreboard values for these statistics are changed.
App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value
as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is
either targetting all global context apps, if no target player has been identified, or only player scoped apps, if the target player either targetting all apps instances for all players, including global apps, if no target player has been identified,
is specified. or only player scoped apps, if the target player
is specified, running once for that player app. You cannot target global apps with player-targeted signals. Built-in events
do target global apps, since their first argument is clearly defined and passed. That may change in the future in case there is
a compelling argument to be able to target global apps with player scopes.
Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have
have no idea why you would need that. The following snippets have the same effect: have no idea why you would need that. The following snippets have the same effect:
@@ -230,11 +243,11 @@ handle_event('player_breaks_block', null);
And `signal_event` can be used as a trigger, called twice for player based built-in events And `signal_event` can be used as a trigger, called twice for player based built-in events
``` ```
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
``` ```
or (for global only events) or (for global events)
``` ```
signal_event('tick') // trigger only global scoped apps with no extra arguments signal_event('tick') // trigger all apps with a tick event
``` ```
### `handle_event(event, callback ...)` ### `handle_event(event, callback ...)`
@@ -248,6 +261,8 @@ If extra arguments are provided, they will be appended to the argument list of t
Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event, Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event,
or incorect number of parameters for the event). or incorect number of parameters for the event).
If a callback is specified as `null`, the given app (or player app instance )stops handling that event.
<pre> <pre>
foo(a) -> print(a); foo(a) -> print(a);
handle_event('boohoo', 'foo'); handle_event('boohoo', 'foo');
@@ -260,113 +275,61 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event
handle_event('tick', null) // nah, ima good, kthxbai handle_event('tick', null) // nah, ima good, kthxbai
</pre> </pre>
In case you want to pass an event handler that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `signal_event(event, target_player?, ... args?)` ### `signal_event(event, target_player?, ... args?)`
Fires a specific event. If the event does not exist (only `handle_event` creates missing mising new events), or provided argument list Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
was not matching the callee expected arguments, returns `null`, was not matching the callee expected arguments, returns `null`,
otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting
only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event. only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event.
If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped. If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
all app scopes. Note that all built-in player events have a player as a first argument, so to trigger these events, you need to
provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function. provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function.
<pre> <pre>
signal_event('player_breaks_block', player, player, block); // to target all player scoped apps signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
signal_event('tick') // triggering tick event in all loaded apps signal_event('tick') // trigger all apps with a tick event
</pre> </pre>
## Custom events example ## Custom events example
The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous The following example shows how you can communicate between different instances of the same player scoped app. It important to note
squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called
in this case the event is once called as a global event, and once as a player event, targeting each app once. Custom events is `tp_request` and is triggered with a command.
a good idea to share common event detection code with one app, and using the outcome of these events in multiple places.
Obviously you would define each event handler once not twice in your app system.
In this
case here we have two ways of detecting all players squatting for more than 40 ticks - one of those apps will produce cloud particles
in an apprpriate location, and the other will produce a sound when it happens, and they both will proceed to signal the same event,
thankfully for different scopes, otherwise other apps would trigger twice.
``` ```
// detector1.sc // tpa.sc
// detecting event in a tick loop global_requester = null;
__config() -> {'scope' -> 'global'}; __config() -> {
'commands' -> {
global_sneaks = {}; '<player>' -> _(to) -> signal_event('tp_request', to, player()),
__on_tick() -> for(player('all'), 'accept' -> _() -> if(global_requester,
if (_~'sneaking', run('tp '+global_requester~'command_name');
sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0); global_requester = null
if (sneak_time > 20 && sneak_time < 40,
particle('cloud', pos(_) + [0, _~'height'/2, 0] - _~'look', 1, 0.1)
);
if (sneak_time == 40,
signal_event('player_farts', _, _) // triggering all player scoped apps from a global app
) )
, // else },
delete(global_sneaks:_) 'arguments' -> {
) 'player' -> {'type' -> 'players', 'single' -> true}
) }
};
handle_event('tp_request', _(req) -> (
global_requester = req;
print(player(), format(
'w '+req+' requested to teleport to you. Click ',
'yb here', '^yb here', '!/tpa accept',
'w to accept it.'
));
));
``` ```
```
// detector2.sc
// detecting events in a more appropriate way - player based and event based app
__on_player_starts_sneaking(p) ->
(
global_sneaking = tick_time();
schedule(40, 'check', p);
);
check(p) ->
if( global_sneaking && tick_time() - global_sneaking == 40,
sound('entity.shulker.shoot', pos(p), 1, 0.6, 'player');
signal_event('player_farts', null, p) // trigerring all global apps from a player scoped app
);
__on_player_stops_sneaking(p) -> (global_sneaking = false);
```
And then two other apps that meant to do something with this event. One mimics an explosion, and the other - grows crops around
the player.
```
client1.sc
__config() -> {'scope' -> 'global'};
on_fart(p, msg) ->
(
print(p+msg);
particle('explosion', pos(p));
sound('entity.generic.explode', pos(p), 1, 0.5);
);
handle_event('player_farts', 'on_fart', ' goes kaboom');
```
```
client2.sc
longsneak(p) ->
(
ppos = pos(p);
loop ( 500,
target = ppos+[rand(12),rand(8),rand(12)]-[rand(12),rand(8),rand(12)];
if (material(target) == 'plant' && ticks_randomly(target),
particle('happy_villager', target, 2, 0.4)
);
random_tick(target)
)
);
handle_event('player_farts', 'longsneak');
```
## `/script event` command ## `/script event` command
used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to used to display current events and bounded functions. use `add_to` to register a new event, or `remove_from` to
unbind a specific function from an event. Function to be bounded to an event needs to have the same number of unbind a specific function from an event. Function to be bounded to an event needs to have the same number of
parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load` parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load`
that have functions listed above will be automatically bounded and unbounded when script is unloaded. that handle specific built-in events will be automatically bounded, and unbounded when script is unloaded.
+8
View File
@@ -28,6 +28,14 @@ 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. 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?)
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?)
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
### `stack_limit(item)` ### `stack_limit(item)`
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
+35 -9
View File
@@ -6,7 +6,9 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript
## App structure ## App structure
The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts` The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts`
folder. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope folder, flat. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world,
and here you can actually organize them in folders.
When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope
(more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the
`__config()` function, if present, which will be executed once. Loading the app will also bind specific `__config()` function, if present, which will be executed once. Loading the app will also bind specific
events to the event system (check Events section for details). events to the event system (check Events section for details).
@@ -32,15 +34,14 @@ the app state and all players interactions run in the same context, sharing defi
explicitly keyed with players, player names, uuids, etc. explicitly keyed with players, player names, uuids, etc.
Even for `'player'` scoped apps, you can access specific player app with with commandblocks using Even for `'player'` scoped apps, you can access specific player app with with commandblocks using
`/execute as <player> run script in <app> run ...`. `/execute as <player> run script in <app> run ...`.
To access global/server state for a player app, you need to disown the command from any player, To access global/server state for a player app, which you shouldn't do, you need to disown the command from any player,
so either use a command block, or any so either use a command block, or any
arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however arbitrary entity: `/execute as @e[type=bat,limit=1] run script in <app> globals` for instance, however
running anything in the global scope for a `'player'` scoped app is not recommended. running anything in the global scope for a `'player'` scoped app is not intended.
* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will * `'stay_loaded'`: defaults to `true`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will
stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down.
This is to allow to store multiple apps on the server/world and selectively decide which one should be running at WARNING: all apps will run once at startup anyways, so be aware that their actions that are called
startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded.
statically, will be performed once anyways.
* `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function, * `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function,
all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type
will be inferred from their names, otherwise will be inferred from their names, otherwise
@@ -50,9 +51,20 @@ conflicts and ambiguities between different paths of execution. While ambiguous
and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet
will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and
`true`, then scarpet will load all provided commands regardless. `true`, then scarpet will load all provided commands regardless.
* `'command_permission'` - indicates a custom permission to run the command. It can either be a number indicating
permission level (from 1 to 4) or a string value, one of: `'all'` (default), `'ops'` (default opped player with permission level of 2),
`'server'` - command accessible only through the server console and commandblocks, but not in chat, `'players'` - opposite
of the former, allowing only use in player chat. It can also be a function (lambda function or function value, not function name)
that takes 1 parameter, which represents the calling player, or `'null'` if the command represents a server call.
The function will prevent the command from running if it evaluates to `false`.
Please note, that Minecraft evaluates eligible commands for players when they join, or on reload/restart, so if you use a
predicate that is volatile and might change, the command might falsely do or do not indicate that it is available to the player,
however player will always be able to type it in and either succeed, or fail, based on their current permissions.
Custom permission applies to legacy commands with `'legacy_command_type_support'` as well
as for the custom commands defined with `'commands'`, see below.
* `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well * `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well
as for the custom commands defined with `'commands'`, see below as for the custom commands defined with `'commands'`, see below.
* `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below * `'commands'` - defines custom commands for the app to be executed with `/<app>` command, see below.
## Custom app commands ## Custom app commands
@@ -205,6 +217,20 @@ Here is a list of built-in types, with their return value formats, as well as a
* `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position * `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position
to be loaded. to be loaded.
* `'block'`: a valid block state wrapped in a block value (including block properties and data) * `'block'`: a valid block state wrapped in a block value (including block properties and data)
* `'blockpredicate`': returns a 4-tuple indicating conditions of a block to match: block name, block tag,
map of required state properties, and tag to match. Either block name or block tag are `null` but not both.
Property map is always specified, but its empty for no conditions, and matching nbt tag can be `null` indicating
no requirements. Technically the 'all-matching' predicate would be `[null, null, {}, null]`, but
block name or block tag is always specified. One can use the following routine to match a block agains this predicate:
```
block_to_match = block(x,y,z);
[block_name, block_tag, properties, nbt_tag] = block_predicate;
(block_name == null || block_name == block_to_match) &&
(block_tag == null || block_tags(block_to_match, block_tag)) &&
all(properties, block_state(block_to_match, _) == properties:_) &&
(!tag || tag_matches(block_data(block_to_match), tag))
```
* `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors. * `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors.
* `'columnpos'`: a pair of x and z coordinates. * `'columnpos'`: a pair of x and z coordinates.
* `'dimension'`: string representing a valid dimension in the world. * `'dimension'`: string representing a valid dimension in the world.
+59
View File
@@ -105,3 +105,62 @@ team_property('admin','seeFriendlyInvisibles',true) Make all players in 'a
team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible team_property('admin','deathMessageVisibility','hideForOtherTeams') Make all players in 'admin' see other admins even when invisible
``` ```
## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)`
Manage bossbars just like with the `/bossbar` command.
Without any arguments, returns a list of all bossbars.
When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar.
Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`.
In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically.
If the id was invalid (for example by having more than one colon), returns `null`.
If the bossbar already exists, returns `false`.
`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified)
`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified
`bossbar('foo:bar:baz') => null` Invalid identifier
`bossbar(id,property)` is used to query the `property` of a bossbar.
`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`.
Available properties are:
* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'`
* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'`
* value: value of the bossbar progress
* max: maximum value of the bossbar progress, by default 100
* name: Text to display above the bossbar, supports formatted text
* visible: whether the bossbar is visible or not
* players: List of players that can see the bossbar
* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present)
* remove: remove this bossbar, no `value` required
```
bossbar('script:test','style','notched_12')
bossbar('script:test','value',74)
bossbar('script:test','name',format('rb Test')) -> Change text
bossbar('script:test','visible',false) -> removes visibility, but keeps players
bossbar('script:test','players',player('all')) -> Visible for all players
bossbar('script:test','players',player('Steve')) -> Visible for Steve only
bossbar('script:test','players',null) -> Invalid player, removing all players
bossbar('script:test','add_player',player('Alex')) -> Add Alex to the list of players that can see the bossbar
bossbar('script:test','remove') -> remove bossbar 'script:test'
for(bossbar(),bossbar(_,'remove')) -> remove all bossbars
```
+7 -5
View File
@@ -8,13 +8,15 @@ to simulate running commands in a different scope.
# `/script load / unload <app> (global?)`, `/script in <app>` commands # `/script load / unload <app> (global?)`, `/script in <app>` commands
`load / unload` commands allow for very conventient way of writing your code, providing it to the game and `load / unload` commands allow for very convenient way of writing your code, providing it to the game and
distribute with your worlds without the need of use of commandblocks. Just place your scarpet code in the distribute with your worlds without the need of use of commandblocks. Just place your Scarpet code in the
/scripts folder of your world files and make sure it ends with `.sc` extension. The good thing about editing that `/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can
code is that you can no only use normal editing without the need of marking of newlines, also save your scripts in `.minecraft/config/carpet/scripts` to make them available in any world.
The good thing about editing that code is that you can not only use normal editing without the need of marking of newlines,
but you can also use comments in your code. but you can also use comments in your code.
a comment is anything that starts with a double slash, and continues to the end of the line: A comment is anything that starts with a double slash, and continues to the end of the line:
<pre> <pre>
foo = 1; foo = 1;
+2
View File
@@ -215,6 +215,8 @@ map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce(range(10),_a+_, 0) => 45 reduce(range(10),_a+_, 0) => 45
range(5,10) => [5, 6, 7, 8, 9] range(5,10) => [5, 6, 7, 8, 9]
range(20, 10, -2) => [20, 18, 16, 14, 12] range(20, 10, -2) => [20, 18, 16, 14, 12]
range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2]
range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2]
</pre> </pre>
## Map operations ## Map operations
@@ -135,7 +135,7 @@ foo(... x) -> ... # all arguments for foo are included in the list
### `import(module_name, symbols ...)` ### `import(module_name, symbols ...)`
Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use
them in the current app. This include other symbols imported by these modules. Scarpet supports cicular dependencies, them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies,
but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them.
Returns full list of available symbols that could be imported from this module, which can be used to debug import Returns full list of available symbols that could be imported from this module, which can be used to debug import
issues, and list contents of libraries. issues, and list contents of libraries.
@@ -144,9 +144,57 @@ issues, and list contents of libraries.
calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly
except you can use it with function value, or name instead. This means you can pass functions to other user defined except you can use it with function value, or name instead. This means you can pass functions to other user defined
functions as arguments and call them with `call` internally. And since function definitions return the defined functions as arguments and call them with `call` internally. Since function definitions return the defined
function, they can be defined in place as anonymous functions. function, they can be defined in place as anonymous functions.
#### Passing function references to other modules of your application
In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports,
meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the
library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app,
it should import back that method from the main module for visibility.
Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in
the parent app:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme('foo' , 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just
a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like
imports in Java classes), and that solves the issue but makes the library dependent on the main app:
```
//lib.scl
import('app','foo');
callme(fun, arg) -> call(fun, arg);
```
You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created
in the module in which the requested function is visible:
```
//app.sc
import('lib', 'callme');
foo(x) -> x*x;
test() -> callme(_(x) -> foo(x), 5);
```
```
//lib.scl
callme(fun, arg) -> call(fun, arg);
```
Or by passing an explicit reference to the function, instead of calling it by name:
```
//app.sc
import('lib', 'callme');
global_foohandler = (foo(x) -> x*x);
test() -> callme(global_foohandler, 5);
```
Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not
creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
programmers with their own lambda arguments programmers with their own lambda arguments
+4 -3
View File
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
## Code delivery, line indicators ## Code delivery, line indicators
Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing
your code in apps (files with `.sc` extension that can be placed inside "/scripts" folder in the world files and your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files
loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded
contain code, no need to start with "/script run" prefix as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only
contain code, no need to start with `/script run` prefix.
The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder
+3
View File
@@ -122,6 +122,9 @@ a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5)
=> Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter. => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
</pre> </pre>
In case you want to create a task based on a function that is not defined in your module, please read the tips on
"Passing function references to other modules of your application" section in the `call(...)` section.
### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` ### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)`
+9 -3
View File
@@ -12,8 +12,10 @@ block_tick
block_tags block_tags
blocks_daylight blocks_daylight
blocks_movement blocks_movement
bossbar
brightness brightness
chunk_tickets chunk_tickets
convert_date
crafting_remaining_item crafting_remaining_item
create_marker create_marker
current_dimension current_dimension
@@ -51,9 +53,12 @@ inventory_remove
inventory_set inventory_set
inventory_size inventory_size
item_category item_category
item_list
item_tags
last_tick_times last_tick_times
light light
liquid liquid
list_files
load_app_data load_app_data
loaded loaded
loaded_status loaded_status
@@ -92,10 +97,10 @@ scoreboard_remove
scoreboard_display scoreboard_display
tag_matches tag_matches
team_add team_add
team_list
team_remove
team_leave team_leave
team_list
team_property team_property
team_remove
read_file read_file
see_sky see_sky
set set
@@ -121,8 +126,9 @@ ticks_randomly
top top
transparent transparent
update update
unix_time
view_distance view_distance
volume volume
without_updates without_updates
world_time world_time
write_file write_file
@@ -58,15 +58,19 @@
structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension
add_chunk_ticket add_chunk_ticket
convert_date unix_time
display_title display_title
scoreboard scoreboard_add scoreboard_remove scoreboard_display scoreboard scoreboard_add scoreboard_remove scoreboard_display
team_add team_list team_remove team_leave team_property team_add team_list team_remove team_leave team_property
bossbar
player spawn entity_id entity_list entity_area entity_selector query modify entity_event entity_load_handler entity_types player spawn entity_id entity_list entity_area entity_selector query modify entity_event entity_load_handler entity_types
stack_limit item_category recipe_data crafting_remaining_item item_list item_tags stack_limit item_category recipe_data crafting_remaining_item
inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item
@@ -80,7 +84,7 @@
plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
load_app_data store_app_data read_file write_file delete_file load_app_data store_app_data read_file write_file delete_file list_files
escape_nbt parse_nbt encode_nbt tag_matches statistic escape_nbt parse_nbt encode_nbt tag_matches statistic
</Keywords> </Keywords>
<Keywords name="Keywords3">euler pi null true false</Keywords> <Keywords name="Keywords3">euler pi null true false</Keywords>
+1 -1
View File
@@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G
loader_version=0.10.8 loader_version=0.10.8
# Mod Properties # Mod Properties
mod_version = 1.4.19+v201125 mod_version = 1.4.21+v201216
maven_group = carpet maven_group = carpet
archives_base_name = fabric-carpet archives_base_name = fabric-carpet
+16 -3
View File
@@ -5,7 +5,19 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import carpet.commands.*; import carpet.commands.CounterCommand;
import carpet.commands.DistanceCommand;
import carpet.commands.DrawCommand;
import carpet.commands.InfoCommand;
import carpet.commands.LogCommand;
import carpet.commands.MobAICommand;
import carpet.commands.PerimeterInfoCommand;
import carpet.commands.PlayerCommand;
import carpet.commands.ProfileCommand;
import carpet.commands.ScriptCommand;
import carpet.commands.SpawnCommand;
import carpet.commands.TestCommand;
import carpet.commands.TickCommand;
import carpet.network.ServerNetworkHandler; import carpet.network.ServerNetworkHandler;
import carpet.helpers.HopperCounter; import carpet.helpers.HopperCounter;
import carpet.helpers.TickSpeed; import carpet.helpers.TickSpeed;
@@ -90,9 +102,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
public static void onServerLoadedWorlds(MinecraftServer minecraftServer) public static void onServerLoadedWorlds(MinecraftServer minecraftServer)
{ {
HopperCounter.resetAll(minecraftServer);
extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer)); extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer));
scriptServer.initializeForWorld(); scriptServer.initializeForWorld();
HopperCounter.resetAll(minecraftServer);
} }
public static void tick(MinecraftServer server) public static void tick(MinecraftServer server)
@@ -143,6 +155,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
{ {
ServerNetworkHandler.onPlayerJoin(player); ServerNetworkHandler.onPlayerJoin(player);
LoggerRegistry.playerConnected(player); LoggerRegistry.playerConnected(player);
scriptServer.onPlayerJoin(player);
extensions.forEach(e -> e.onPlayerLoggedIn(player)); extensions.forEach(e -> e.onPlayerLoggedIn(player));
} }
@@ -156,9 +169,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
public static void onServerClosed(MinecraftServer server) public static void onServerClosed(MinecraftServer server)
{ {
if (scriptServer != null) scriptServer.onClose();
ServerNetworkHandler.close(); ServerNetworkHandler.close();
currentCommandDispatcher = null; currentCommandDispatcher = null;
if (scriptServer != null) scriptServer.onClose();
LoggerRegistry.stopLoggers(); LoggerRegistry.stopLoggers();
extensions.forEach(e -> e.onServerClosed(server)); extensions.forEach(e -> e.onServerClosed(server));
+2 -2
View File
@@ -36,7 +36,7 @@ import static carpet.settings.RuleCategory.CLIENT;
@SuppressWarnings("CanBeFinal") @SuppressWarnings("CanBeFinal")
public class CarpetSettings public class CarpetSettings
{ {
public static final String carpetVersion = "1.4.19+v201125"; public static final String carpetVersion = "1.4.21+v201216";
public static final Logger LOG = LogManager.getLogger("carpet"); public static final Logger LOG = LogManager.getLogger("carpet");
public static boolean skipGenerationChecks = false; public static boolean skipGenerationChecks = false;
public static boolean impendingFillSkipUpdates = false; public static boolean impendingFillSkipUpdates = false;
@@ -427,7 +427,7 @@ public class CarpetSettings
extra = "if /script is enabled", extra = "if /script is enabled",
category = SCARPET category = SCARPET
) )
public static boolean scriptsAutoload = false; public static boolean scriptsAutoload = true;
@Rule(desc = "Enables /player command to control/spawn players", category = COMMAND) @Rule(desc = "Enables /player command to control/spawn players", category = COMMAND)
public static String commandPlayer = "ops"; public static String commandPlayer = "ops";
@@ -58,6 +58,9 @@ public class PlayerCommand
.then(makeActionCommand("dropStack", EntityPlayerActionPack.ActionType.DROP_STACK)) .then(makeActionCommand("dropStack", EntityPlayerActionPack.ActionType.DROP_STACK))
.then(makeDropCommand("dropStack", true)) .then(makeDropCommand("dropStack", true))
.then(makeActionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS)) .then(makeActionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS))
.then(literal("hotbar")
.then(argument("slot", IntegerArgumentType.integer(1, 9))
.executes(c -> manipulate(c, ap -> ap.setSlot(IntegerArgumentType.getInteger(c, "slot"))))))
.then(literal("kill").executes(PlayerCommand::kill)) .then(literal("kill").executes(PlayerCommand::kill))
.then(literal("shadow"). executes(PlayerCommand::shadow)) .then(literal("shadow"). executes(PlayerCommand::shadow))
.then(literal("mount").executes(manipulation(ap -> ap.mount(true))) .then(literal("mount").executes(manipulation(ap -> ap.mount(true)))
@@ -18,6 +18,7 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder; import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.block.Block; import net.minecraft.block.Block;
@@ -84,7 +85,7 @@ public class ScriptCommand
private static CompletableFuture<Suggestions> suggestCode( private static CompletableFuture<Suggestions> suggestCode(
CommandContext<ServerCommandSource> context, CommandContext<ServerCommandSource> context,
SuggestionsBuilder suggestionsBuilder SuggestionsBuilder suggestionsBuilder
) ) throws CommandSyntaxException
{ {
CarpetScriptHost currentHost = getHost(context); CarpetScriptHost currentHost = getHost(context);
String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
@@ -291,7 +292,7 @@ public class ScriptCommand
)?1:0)))))). )?1:0)))))).
then(literal("remove_from"). then(literal("remove_from").
then(argument("event", StringArgumentType.word()). then(argument("event", StringArgumentType.word()).
suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().map(ev -> ev.name).collect(Collectors.toList()) ,bb)). suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().filter(CarpetEventServer.Event::isNeeded).map(ev -> ev.name).collect(Collectors.toList()) ,bb)).
then(argument("call", StringArgumentType.greedyString()). then(argument("call", StringArgumentType.greedyString()).
suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.getEvent(StringArgumentType.getString(cc, "event"), CarpetServer.scriptServer).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)). suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.getEvent(StringArgumentType.getString(cc, "event"), CarpetServer.scriptServer).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)).
executes( (cc) -> CarpetServer.scriptServer.events.removeEventFromCommand( executes( (cc) -> CarpetServer.scriptServer.events.removeEventFromCommand(
@@ -311,14 +312,14 @@ public class ScriptCommand
suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(), bb)). suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(), bb)).
then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t)))); then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t))));
} }
private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context) private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context) throws CommandSyntaxException
{ {
CarpetScriptHost host; CarpetScriptHost host;
try try
{ {
String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT); String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT);
CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost); CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost);
host = parentHost.retrieveForExecution(context.getSource()); host = parentHost.retrieveOwnForExecution(context.getSource());
} }
catch (IllegalArgumentException ignored) catch (IllegalArgumentException ignored)
{ {
@@ -327,7 +328,7 @@ public class ScriptCommand
host.setChatErrorSnooper(context.getSource()); host.setChatErrorSnooper(context.getSource());
return host; return host;
} }
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException
{ {
CarpetScriptHost host = getHost(c); CarpetScriptHost host = getHost(c);
return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()); return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList());
@@ -350,7 +351,7 @@ public class ScriptCommand
} }
return 1; return 1;
} }
private static int listGlobals(CommandContext<ServerCommandSource> context, boolean all) private static int listGlobals(CommandContext<ServerCommandSource> context, boolean all) throws CommandSyntaxException
{ {
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
@@ -425,7 +426,7 @@ public class ScriptCommand
//host.resetErrorSnooper(); // lets say no need to reset the snooper in case something happens on the way //host.resetErrorSnooper(); // lets say no need to reset the snooper in case something happens on the way
} }
private static int invoke(CommandContext<ServerCommandSource> context, String call, BlockPos pos1, BlockPos pos2, String args) private static int invoke(CommandContext<ServerCommandSource> context, String call, BlockPos pos1, BlockPos pos2, String args) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -453,7 +454,7 @@ public class ScriptCommand
} }
private static int compute(CommandContext<ServerCommandSource> context, String expr) private static int compute(CommandContext<ServerCommandSource> context, String expr) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -463,7 +464,7 @@ public class ScriptCommand
}); });
} }
private static int scriptScan(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr) private static int scriptScan(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -512,7 +513,7 @@ public class ScriptCommand
private static int scriptFill(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr, private static int scriptFill(CommandContext<ServerCommandSource> context, BlockPos origin, BlockPos a, BlockPos b, String expr,
BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode) BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode) throws CommandSyntaxException
{ {
ServerCommandSource source = context.getSource(); ServerCommandSource source = context.getSource();
CarpetScriptHost host = getHost(context); CarpetScriptHost host = getHost(context);
@@ -3,4 +3,5 @@ package carpet.fakes;
public interface HungerManagerInterface { public interface HungerManagerInterface {
float getExhaustionCM(); float getExhaustionCM();
void setExhaustionCM(float exhaust); void setExhaustionCM(float exhaust);
void setSaturationCM(float saturn);
} }
@@ -0,0 +1,7 @@
package carpet.fakes;
import net.minecraft.world.level.ServerWorldProperties;
public interface ServerWorldInterface {
ServerWorldProperties getWorldPropertiesCM();
}
@@ -10,6 +10,7 @@ import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.vehicle.BoatEntity; import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.entity.vehicle.MinecartEntity; import net.minecraft.entity.vehicle.MinecartEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.s2c.play.HeldItemChangeS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
@@ -276,6 +277,12 @@ public class EntityPlayerActionPack
} }
} }
public void setSlot(int slot)
{
player.inventory.selectedSlot = slot-1;
player.networkHandler.sendPacket(new HeldItemChangeS2CPacket(slot-1));
}
public enum ActionType public enum ActionType
{ {
USE(true) USE(true)
@@ -132,7 +132,8 @@ public class OptimizedExplosion
if (d12 <= 1.0D) { if (d12 <= 1.0D) {
double d5 = entity.getX() - eAccess.getX(); double d5 = entity.getX() - eAccess.getX();
double d7 = entity.getY() + (double) entity.getStandingEyeHeight() - eAccess.getY(); // Change in 1.16 snapshots to fix a bug with TNT jumping
double d7 = (entity instanceof TntEntity ? entity.getY() : entity.getEyeY()) - eAccess.getY();
double d9 = entity.getZ() - eAccess.getZ(); double d9 = entity.getZ() - eAccess.getZ();
double d13 = (double) MathHelper.sqrt(d5 * d5 + d7 * d7 + d9 * d9); double d13 = (double) MathHelper.sqrt(d5 * d5 + d7 * d7 + d9 * d9);
@@ -8,13 +8,11 @@ import org.spongepowered.asm.mixin.Shadow;
@Mixin(HungerManager.class) @Mixin(HungerManager.class)
public class HungerManagerMixin_scarpetEventsMixin implements HungerManagerInterface { public class HungerManagerMixin_scarpetEventsMixin implements HungerManagerInterface {
@Shadow private float exhaustion; @Shadow private float exhaustion;
public float getExhaustionCM(){ @Shadow private float foodSaturationLevel;
return exhaustion;
}
@Override @Override
public void setExhaustionCM(float exhaust) public float getExhaustionCM(){ return exhaustion; }
{ @Override
exhaustion = exhaust; public void setExhaustionCM(float exhaust) { exhaustion = exhaust; }
} @Override
public void setSaturationCM(float saturn) { foodSaturationLevel = saturn; }
} }
@@ -69,7 +69,7 @@ public abstract class PlayerManager_fakePlayersMixin
ServerPlayerEntity serverPlayerEntity_3 = (ServerPlayerEntity) var5.next(); ServerPlayerEntity serverPlayerEntity_3 = (ServerPlayerEntity) var5.next();
if(serverPlayerEntity_3 instanceof EntityPlayerMPFake) if(serverPlayerEntity_3 instanceof EntityPlayerMPFake)
{ {
serverPlayerEntity_3.kill(); ((EntityPlayerMPFake)serverPlayerEntity_3).kill(new TranslatableText("multiplayer.disconnect.duplicate_login"));
continue; continue;
} }
serverPlayerEntity_3.networkHandler.disconnect(new TranslatableText("multiplayer.disconnect.duplicate_login")); serverPlayerEntity_3.networkHandler.disconnect(new TranslatableText("multiplayer.disconnect.duplicate_login"));
@@ -1,6 +1,7 @@
package carpet.mixins; package carpet.mixins;
import carpet.CarpetSettings; import carpet.CarpetSettings;
import carpet.fakes.ServerWorldInterface;
import carpet.script.CarpetEventServer; import carpet.script.CarpetEventServer;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LightningEntity; import net.minecraft.entity.LightningEntity;
@@ -9,7 +10,9 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.profiler.Profiler; import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.chunk.WorldChunk; import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.level.ServerWorldProperties;
import org.spongepowered.asm.mixin.Mixin; 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.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -19,7 +22,7 @@ import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
import static carpet.script.CarpetEventServer.Event.LIGHTNING; import static carpet.script.CarpetEventServer.Event.LIGHTNING;
@Mixin(ServerWorld.class) @Mixin(ServerWorld.class)
public class ServerWorld_scarpetEventMixin public class ServerWorld_scarpetMixin implements ServerWorldInterface
{ {
@Inject(method = "tickChunk", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( @Inject(method = "tickChunk", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(
value = "INVOKE", value = "INVOKE",
@@ -54,4 +57,9 @@ public class ServerWorld_scarpetEventMixin
} }
} }
@Shadow
private ServerWorldProperties worldProperties;
public ServerWorldProperties getWorldPropertiesCM(){
return worldProperties;
}
} }
@@ -0,0 +1,34 @@
package carpet.mixins;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.slot.TradeOutputSlot;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.village.Merchant;
import net.minecraft.village.MerchantInventory;
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.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import static carpet.script.CarpetEventServer.Event.PLAYER_TRADES;
@Mixin(value = TradeOutputSlot.class)
public abstract class TradeOutputSlot_scarpetEventMixin {
@Shadow @Final private Merchant merchant;
@Shadow @Final private MerchantInventory merchantInventory;
@Inject(method = "onTakeItem", at = @At(value = "INVOKE",
target = "Lnet/minecraft/village/Merchant;trade(Lnet/minecraft/village/TradeOffer;)V")
)
private void onTrade(PlayerEntity player, ItemStack stack, CallbackInfoReturnable<ItemStack> cir) {
if(PLAYER_TRADES.isNeeded() && !this.merchant.getMerchantWorld().isClient())
{
PLAYER_TRADES.onTrade((ServerPlayerEntity) player, merchant, merchantInventory.getTradeOffer());
}
}
}
@@ -217,6 +217,13 @@ public class ServerNetworkHandler
} }
public static String getPlayerStatus(ServerPlayerEntity player)
{
if (remoteCarpetPlayers.containsKey(player)) return "carpet "+remoteCarpetPlayers.get(player);
if (validCarpetPlayers.contains(player)) return "carpet "+CarpetSettings.carpetVersion;
return "vanilla";
}
private static class DataBuilder private static class DataBuilder
{ {
private CompoundTag tag; private CompoundTag tag;
@@ -9,6 +9,7 @@ import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.HungerManager; import net.minecraft.entity.player.HungerManager;
import net.minecraft.network.NetworkSide; import net.minecraft.network.NetworkSide;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText; import net.minecraft.text.TranslatableText;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTask; import net.minecraft.server.ServerTask;
@@ -95,9 +96,14 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
@Override @Override
public void kill() public void kill()
{
kill(Messenger.s("Killed"));
}
public void kill(Text reason)
{ {
this.server.send(new ServerTask(this.server.getTicks(), () -> { this.server.send(new ServerTask(this.server.getTicks(), () -> {
this.networkHandler.onDisconnected(Messenger.s("Killed")); this.networkHandler.onDisconnected(reason);
})); }));
} }
@@ -120,6 +126,12 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
super.onDeath(cause); super.onDeath(cause);
setHealth(20); setHealth(20);
this.hungerManager = new HungerManager(); this.hungerManager = new HungerManager();
kill(); kill(this.getDamageTracker().getDeathMessage());
}
@Override
public String getIp()
{
return "127.0.0.1";
} }
} }
@@ -6,6 +6,7 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler
{ {
@@ -22,6 +23,10 @@ public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler
@Override @Override
public void disconnect(Text message) public void disconnect(Text message)
{ {
if (player instanceof EntityPlayerMPFake && message instanceof TranslatableText && ((TranslatableText) message).getKey().equals("multiplayer.disconnect.idling"))
{
((EntityPlayerMPFake) player).kill(new TranslatableText(((TranslatableText) message).getKey()));
}
} }
} }
@@ -14,9 +14,11 @@ import carpet.script.value.Value;
import carpet.script.value.ValueConversions; import carpet.script.value.ValueConversions;
import carpet.settings.ParsedRule; import carpet.settings.ParsedRule;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.passive.MerchantEntity;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -34,6 +36,8 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey; import net.minecraft.util.registry.RegistryKey;
import net.minecraft.village.Merchant;
import net.minecraft.village.TradeOffer;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.Chunk;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@@ -47,8 +51,9 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@@ -65,59 +70,84 @@ public class CarpetEventServer
public static class Callback public static class Callback
{ {
public final String host; public final String host;
public final String optionalTarget;
public final FunctionValue function; public final FunctionValue function;
public final List<Value> parametrizedArgs; public final List<Value> parametrizedArgs;
public Callback(String host, FunctionValue function, List<Value> parametrizedArgs) public Callback(String host, String target, FunctionValue function, List<Value> parametrizedArgs)
{ {
this.host = host; this.host = host;
this.function = function; this.function = function;
this.optionalTarget = target;
this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs; this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs;
} }
/** /**
* Used also in entity events * Used also in entity events
* @param asSource - entity command source * @param sender - entity command source
* @param runtimeArgs = options * @param runtimeArgs = options
*/ */
public boolean execute(ServerCommandSource asSource, List<Value> runtimeArgs) public boolean execute(ServerCommandSource sender, List<Value> runtimeArgs)
{ {
if (this.parametrizedArgs.isEmpty()) if (!this.parametrizedArgs.isEmpty())
return CarpetServer.scriptServer.runas(
asSource.withLevel(CarpetSettings.runPermissionLevel),
host, function, runtimeArgs);
else
{ {
List<Value> combinedArgs = new ArrayList<>(runtimeArgs); runtimeArgs = new ArrayList<>(runtimeArgs);
combinedArgs.addAll(this.parametrizedArgs); runtimeArgs.addAll(this.parametrizedArgs);
return CarpetServer.scriptServer.runas(
asSource.withLevel(CarpetSettings.runPermissionLevel),
host, function, combinedArgs);
} }
return CarpetServer.scriptServer.runEventCall(
sender.withLevel(CarpetSettings.runPermissionLevel),
host, optionalTarget, function, runtimeArgs
);
} }
/** /**
* Used also in entity events * Used also in entity events
* @param recipient - optional target player argument * @param sender - sender of the signal
* @param optionalRecipient - optional target player argument
* @param runtimeArgs = options * @param runtimeArgs = options
*/ */
public Boolean targetPlayer(ServerPlayerEntity recipient, List<Value> runtimeArgs) public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, List<Value> runtimeArgs)
{ {
// recipent of the call doesn't match the handlingHost
if (optionalRecipient != null && !optionalRecipient.getEntityName().equals(optionalTarget))
return 0;
List<Value> args = runtimeArgs; List<Value> args = runtimeArgs;
if (!this.parametrizedArgs.isEmpty()) if (!this.parametrizedArgs.isEmpty())
{ {
args = new ArrayList<>(runtimeArgs); args = new ArrayList<>(runtimeArgs);
args.addAll(this.parametrizedArgs); args.addAll(this.parametrizedArgs);
} }
return CarpetServer.scriptServer.runForPlayer(recipient, host, function, args); return CarpetServer.scriptServer.signal(sender, optionalRecipient, host, function, args, false);
} }
@Override @Override
public String toString() public String toString()
{ {
return function.getString()+((host==null)?"":"(from "+host+")"); return function.getString()+((host==null)?"":"(from "+host+(optionalTarget == null?"":"/"+optionalTarget)+")");
}
public static class Signature
{
String function;
String host;
String target;
public Signature(String fun, String h, String t)
{
function = fun;
host = h;
target = t;
}
}
public static Signature fromString(String str)
{
Pattern find = Pattern.compile("(\\w+)(?:\\(from (\\w+)(?:/(\\w+))?\\))?");
Matcher matcher = find.matcher(str);
if(matcher.matches())
{
return new Signature(matcher.group(1), matcher.group(2), matcher.group(3));
}
return new Signature(str, null, null);
} }
} }
@@ -129,7 +159,8 @@ public class CarpetEventServer
public ScheduledCall(CarpetContext context, FunctionValue function, List<Value> args, long dueTime) public ScheduledCall(CarpetContext context, FunctionValue function, List<Value> args, long dueTime)
{ {
super(context.host.getName(), function, args); // ignoring target as we will be always calling self
super(context.host.getName(), null, function, args);
this.ctx = context; this.ctx = context;
this.dueTime = dueTime; this.dueTime = dueTime;
} }
@@ -139,7 +170,7 @@ public class CarpetEventServer
*/ */
public void execute() public void execute()
{ {
CarpetServer.scriptServer.runas(ctx.origin, ctx.s, host, function, parametrizedArgs); CarpetServer.scriptServer.runScheduledCall(ctx.origin, ctx.s, host, (CarpetScriptHost) ctx.host, function, parametrizedArgs);
} }
@@ -173,7 +204,7 @@ public class CarpetEventServer
} }
} }
public int callCustom(ServerPlayerEntity receipient, List<Value> callArg) public int signal(ServerCommandSource sender, ServerPlayerEntity optinoalReceipient, List<Value> callArg)
{ {
if (callList.isEmpty()) return 0; if (callList.isEmpty()) return 0;
//List<Callback> fails = new ArrayList<>(); //List<Callback> fails = new ArrayList<>();
@@ -181,15 +212,13 @@ public class CarpetEventServer
int successes = 0; int successes = 0;
for (Callback call: callList) for (Callback call: callList)
{ {
Boolean result = call.targetPlayer(receipient, callArg); successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg));
//if (result == Boolean.FALSE) fails.add(call);
if (result == Boolean.TRUE) successes++;
} }
//for (Callback call : fails) callList.remove(call); //for (Callback call : fails) callList.remove(call);
return successes; return successes;
} }
public boolean addEventCall(ServerCommandSource source, String hostName, String funName, Function<ScriptHost, Boolean> verifier) public boolean addFromExternal(ServerCommandSource source, String hostName, String funName, Consumer<ScriptHost> hostOnEventHandler)
{ {
ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName); ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName);
if (host == null) if (host == null)
@@ -198,11 +227,7 @@ public class CarpetEventServer
Messenger.m(source, "r Unknown app "+hostName); Messenger.m(source, "r Unknown app "+hostName);
return false; return false;
} }
if (!verifier.apply(host)) hostOnEventHandler.accept(host);
{
Messenger.m(source, "r Global event can only be added to apps with global scope");
return false;
}
FunctionValue udf = host.getFunction(funName); FunctionValue udf = host.getFunction(funName);
if (udf == null || udf.getArguments().size() != reqArgs) if (udf == null || udf.getArguments().size() != reqArgs)
{ {
@@ -210,11 +235,24 @@ public class CarpetEventServer
Messenger.m(source, "r Callback doesn't expect required number of arguments: "+reqArgs); Messenger.m(source, "r Callback doesn't expect required number of arguments: "+reqArgs);
return false; return false;
} }
String target = null;
if (host.isPerUser())
{
try
{
target = source.getPlayer().getEntityName();
}
catch (CommandSyntaxException e)
{
Messenger.m(source, "r Cannot add event to a player scoped app from a command without a player context");
return false;
}
}
//all clear //all clear
//remove duplicates //remove duplicates
removeEventCall(hostName, udf.getString()); removeEventCall(hostName, target, udf.getString());
callList.add(new Callback(hostName, udf, null)); callList.add(new Callback(hostName, target, udf, null));
return true; return true;
} }
public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List<Value> args) public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List<Value> args)
@@ -225,20 +263,39 @@ public class CarpetEventServer
} }
//all clear //all clear
//remove duplicates //remove duplicates
removeEventCall(host.getName(), function.getString()); removeEventCall(host.getName(), host.user, function.getString());
callList.add(new Callback(host.getName(), function, args)); callList.add(new Callback(host.getName(), host.user, function, args));
return true; return true;
} }
public void removeEventCall(String hostName, String funName) public void removeEventCall(String hostName, String target, String funName)
{ {
callList.removeIf((c)-> c.function.getString().equals(funName) && ( hostName == c.host || (c.host != null && c.host.equalsIgnoreCase(hostName) )) ); callList.removeIf((c)-> c.function.getString().equals(funName)
&& (Objects.equals(c.host, hostName))
&& (Objects.equals(c.optionalTarget, target))
);
} }
public void removeAllCalls(CarpetScriptHost host) public void removeAllCalls(CarpetScriptHost host)
{ {
callList.removeIf((c)-> c.host != null && c.host.equals(host.getName())); callList.removeIf((c)-> (Objects.equals(c.host, host.getName()))
&& (Objects.equals(c.optionalTarget, host.user)));
} }
public void createChildEvents(CarpetScriptHost host)
{
List<Callback> copyCalls = new ArrayList<>();
callList.forEach((c)->
{
if ((Objects.equals(c.host, host.getName()))
&& c.optionalTarget == null)
{
copyCalls.add(new Callback(c.host, host.user, c.function, c.parametrizedArgs));
}
});
callList.addAll(copyCalls);
}
public void clearEverything() public void clearEverything()
{ {
callList.clear(); callList.clear();
@@ -255,6 +312,30 @@ public class CarpetEventServer
return events; return events;
} }
public static final Event START = new Event("server_starts", 0, true)
{
@Override
public void onTick()
{
handler.call(Collections::emptyList, () ->
CarpetServer.minecraft_server.getCommandSource().
withWorld(CarpetServer.minecraft_server.getWorld(World.OVERWORLD))
);
}
};
public static final Event SHUTDOWN = new Event("server_shuts_down", 0, true)
{
@Override
public void onTick()
{
handler.call(Collections::emptyList, () ->
CarpetServer.minecraft_server.getCommandSource().
withWorld(CarpetServer.minecraft_server.getWorld(World.OVERWORLD))
);
}
};
public static final Event TICK = new Event("tick", 0, true) public static final Event TICK = new Event("tick", 0, true)
{ {
@Override @Override
@@ -470,6 +551,20 @@ public class CarpetEventServer
), player::getCommandSource); ), player::getCommandSource);
} }
}; };
public static final Event PLAYER_TRADES = new Event("player_trades", 5, false)
{
@Override
public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer)
{
handler.call( () -> Arrays.asList(
new EntityValue(player),
merchant instanceof MerchantEntity ? new EntityValue((MerchantEntity) merchant) : Value.NULL,
ValueConversions.of(tradeOffer.getOriginalFirstBuyItem()),
ValueConversions.of(tradeOffer.getSecondBuyItem()),
ValueConversions.of(tradeOffer.getSellItem())
), player::getCommandSource);
}
};
public static final Event PLAYER_PICKS_UP_ITEM = new Event("player_picks_up_item", 2, false) public static final Event PLAYER_PICKS_UP_ITEM = new Event("player_picks_up_item", 2, false)
{ {
@Override @Override
@@ -801,6 +896,12 @@ public class CarpetEventServer
host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.removeAllCalls(host)); host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.removeAllCalls(host));
} }
public static void transferAllHostEventsToChild(CarpetScriptHost host)
{
byName.values().forEach((e) -> e.handler.createChildEvents(host));
host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.createChildEvents(host));
}
public static void clearAllBuiltinEvents() public static void clearAllBuiltinEvents()
{ {
byName.values().forEach(e -> e.handler.clearEverything()); byName.values().forEach(e -> e.handler.clearEverything());
@@ -842,6 +943,7 @@ public class CarpetEventServer
public void onDamage(Entity target, float amount, DamageSource source) { } public void onDamage(Entity target, float amount, DamageSource source) { }
public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {} public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {}
public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {} public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {}
public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {}
public void onWorldEvent(ServerWorld world, BlockPos pos) { } public void onWorldEvent(ServerWorld world, BlockPos pos) { }
@@ -888,7 +990,7 @@ public class CarpetEventServer
{ {
return false; return false;
} }
boolean added = ev.handler.addEventCall(source, host, funName, h -> canAddEvent(ev, h)); boolean added = ev.handler.addFromExternal(source, host, funName, h -> onEventAddedToHost(ev, h));
if (added) Messenger.m(source, "gi Added " + funName + " to " + event); if (added) Messenger.m(source, "gi Added " + funName + " to " + event);
return added; return added;
} }
@@ -897,8 +999,7 @@ public class CarpetEventServer
{ {
// this is globals only // this is globals only
Event ev = Event.byName.get(event); Event ev = Event.byName.get(event);
if (!canAddEvent(ev, host)) onEventAddedToHost(ev, host);
throw new InternalExpressionException("Global event "+event+" can only be added to apps with global scope");
boolean success = ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); boolean success = ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args);
if (!success) throw new InternalExpressionException("Global event "+event+" requires "+ev.handler.reqArgs+", not "+(function.getNumParams()-((args==null)?0:args.size()))); if (!success) throw new InternalExpressionException("Global event "+event+" requires "+ev.handler.reqArgs+", not "+(function.getNumParams()-((args==null)?0:args.size())));
} }
@@ -906,22 +1007,21 @@ public class CarpetEventServer
public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List<Value> args) public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List<Value> args)
{ {
Event ev = Event.getOrCreateCustom(event, host.getScriptServer()); Event ev = Event.getOrCreateCustom(event, host.getScriptServer());
if (!canAddEvent(ev, host)) onEventAddedToHost(ev, host);
throw new InternalExpressionException("Global built-in event "+event+" can only be handled in apps with global scope");
return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args);
} }
public int signalEvent(String event, CarpetScriptHost host, ServerPlayerEntity player, List<Value> callArgs) public int signalEvent(String event, CarpetContext cc, ServerPlayerEntity optionalTarget, List<Value> callArgs)
{ {
Event ev = Event.getEvent(event, host.getScriptServer()); Event ev = Event.getEvent(event, ((CarpetScriptHost)cc.host).getScriptServer());
if (ev == null) return -1; if (ev == null) return -1;
return ev.handler.callCustom(player, callArgs); return ev.handler.signal(cc.s, optionalTarget, callArgs);
} }
private boolean canAddEvent(Event event, ScriptHost host) private void onEventAddedToHost(Event event, ScriptHost host)
{ {
if (event.deprecated()) host.issueDeprecation(event.name+" event"); if (event.deprecated()) host.issueDeprecation(event.name+" event");
return !(event.globalOnly && (host.perUser || host.parent != null)); //return !(event.globalOnly && (host.perUser || host.parent != null));
} }
public boolean removeEventFromCommand(ServerCommandSource source, String event, String funName) public boolean removeEventFromCommand(ServerCommandSource source, String event, String funName)
@@ -932,8 +1032,8 @@ public class CarpetEventServer
Messenger.m(source, "r Unknown event: " + event); Messenger.m(source, "r Unknown event: " + event);
return false; return false;
} }
Pair<String,String> call = decodeCallback(funName); Callback.Signature call = Callback.fromString(funName);
ev.handler.removeEventCall(call.getLeft(), call.getRight()); ev.handler.removeEventCall(call.host, call.target, call.function);
// could verified if actually removed // could verified if actually removed
Messenger.m(source, "gi Removed event: " + funName + " from "+event); Messenger.m(source, "gi Removed event: " + funName + " from "+event);
return true; return true;
@@ -949,13 +1049,15 @@ public class CarpetEventServer
public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName) public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName)
{ {
Event ev = Event.getEvent(event, host.getScriptServer()); Event ev = Event.getEvent(event, host.getScriptServer());
if (ev != null) ev.handler.removeEventCall(host.getName(), funName); if (ev != null) ev.handler.removeEventCall(host.getName(), host.user, funName);
} }
public void removeAllHostEvents(CarpetScriptHost host) public void removeAllHostEvents(CarpetScriptHost host)
{ {
// remove event handlers // remove event handlers
Event.removeAllHostEvents(host); Event.removeAllHostEvents(host);
if (host.isPerUser())
for (ScriptHost child: host.userHosts.values()) Event.removeAllHostEvents((CarpetScriptHost) child);
// remove scheduled calls // remove scheduled calls
scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName())); scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName()));
} }
+101 -20
View File
@@ -1,6 +1,7 @@
package carpet.script; package carpet.script;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.api.Auxiliary; import carpet.script.api.Auxiliary;
import carpet.script.argument.FunctionArgument; import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
@@ -10,12 +11,12 @@ import carpet.script.exception.CarpetExpressionException;
import carpet.script.exception.ExpressionException; import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.exception.InvalidCallbackException; import carpet.script.exception.InvalidCallbackException;
import carpet.script.value.EntityValue;
import carpet.script.value.FunctionValue; import carpet.script.value.FunctionValue;
import carpet.script.value.MapValue; import carpet.script.value.MapValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.StringValue; import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.CarpetSettings;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@@ -38,9 +39,11 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.lang.Math.max; import static java.lang.Math.max;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.server.command.CommandManager.literal;
@@ -117,7 +120,7 @@ public class CarpetScriptHost extends ScriptHost
private static int execute(CommandContext<ServerCommandSource> ctx, String hostName, FunctionArgument<Value> funcSpec, List<String> paramNames) throws CommandSyntaxException private static int execute(CommandContext<ServerCommandSource> ctx, String hostName, FunctionArgument<Value> funcSpec, List<String> paramNames) throws CommandSyntaxException
{ {
CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveForExecution(ctx.getSource()); CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource());
List<String> argNames = funcSpec.function.getArguments(); List<String> argNames = funcSpec.function.getArguments();
if ((argNames.size()-funcSpec.args.size()) != paramNames.size()) if ((argNames.size()-funcSpec.args.size()) != paramNames.size())
throw new SimpleCommandExceptionType(new LiteralText("Target function "+funcSpec.function.getPrettyString()+" as wrong number of arguments, required "+paramNames.size()+", found "+argNames.size()+" with "+funcSpec.args.size()+" provided")).create(); throw new SimpleCommandExceptionType(new LiteralText("Target function "+funcSpec.function.getPrettyString()+" as wrong number of arguments, required "+paramNames.size()+", found "+argNames.size()+" with "+funcSpec.args.size()+" provided")).create();
@@ -162,8 +165,9 @@ public class CarpetScriptHost extends ScriptHost
) throws CommandSyntaxException ) throws CommandSyntaxException
{ {
String hostName = main.getName(); String hostName = main.getName();
Function<ServerCommandSource, Boolean> configValidator = getCommandConfigPermissions();
LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName). LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName).
requires((player) -> CarpetServer.scriptServer.modules.containsKey(hostName) && useValidator.apply(player)); requires((player) -> CarpetServer.scriptServer.modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player));
for (Pair<List<CommandToken>,FunctionArgument<Value>> commandData : entries) for (Pair<List<CommandToken>,FunctionArgument<Value>> commandData : entries)
{ {
command = this.addPathToCommand(command, commandData.getKey(), commandData.getValue()); command = this.addPathToCommand(command, commandData.getKey(), commandData.getValue());
@@ -171,12 +175,63 @@ public class CarpetScriptHost extends ScriptHost
return command; return command;
} }
public Function<ServerCommandSource, Boolean> getCommandConfigPermissions() throws CommandSyntaxException
{
Value confValue = appConfig.get(StringValue.of("command_permission"));
if (confValue == null) return s -> true;
if (confValue instanceof NumericValue)
{
int level = ((NumericValue) confValue).getInt();
if (level < 1 || level > 4) throw CommandArgument.error("Numeric permission level for custom commands should be between 1 and 4");
return s -> s.hasPermissionLevel(level);
}
if (!(confValue instanceof FunctionValue))
{
String perm = confValue.getString().toLowerCase(Locale.ROOT);
switch (perm)
{
case "ops": return s -> s.hasPermissionLevel(2);
case "server": return s -> !(s.getEntity() instanceof ServerPlayerEntity);
case "players": return s -> s.getEntity() instanceof ServerPlayerEntity;
case "all": return s -> true;
default: throw CommandArgument.error("Unknown command permission: "+perm);
}
}
FunctionValue fun = (FunctionValue) confValue;
if (fun.getNumParams() != 1) throw CommandArgument.error("Custom command permission function should expect 1 argument");
String hostName = getName();
return s -> {
try
{
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();
}
catch (CommandSyntaxException e)
{
Messenger.m(s, "rb Unable to run app command: "+e.getMessage());
return false;
}
};
}
@Override @Override
protected ScriptHost duplicate() protected ScriptHost duplicate()
{ {
return new CarpetScriptHost(scriptServer, main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp); return new CarpetScriptHost(scriptServer, main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp);
} }
@Override
protected void transferToChild(ScriptHost host)
{
super.transferToChild(host);
// transfer Events
CarpetEventServer.Event.transferAllHostEventsToChild((CarpetScriptHost) host);
}
@Override @Override
public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function) public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function)
{ {
@@ -208,7 +263,7 @@ public class CarpetScriptHost extends ScriptHost
if (!(ret instanceof MapValue)) return false; if (!(ret instanceof MapValue)) return false;
Map<Value, Value> config = ((MapValue) ret).getMap(); Map<Value, Value> config = ((MapValue) ret).getMap();
setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player")); setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player"));
persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.FALSE).getBoolean(); persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.TRUE).getBoolean();
// read custom arguments // read custom arguments
Value arguments = config.get(StringValue.of("arguments")); Value arguments = config.get(StringValue.of("arguments"));
if (arguments != null) if (arguments != null)
@@ -319,38 +374,58 @@ public class CarpetScriptHost extends ScriptHost
} }
} }
public CarpetScriptHost retrieveForExecution(ServerCommandSource source) public List<CarpetScriptHost> retrieveForExecution(ServerCommandSource source, String optionalTarget)
{ {
CarpetScriptHost host = this; List<CarpetScriptHost> targets = new ArrayList<>();
if (perUser) if (perUser)
{ {
try if (optionalTarget == null)
{ {
ServerPlayerEntity player = source.getPlayer(); for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList())
host = (CarpetScriptHost) retrieveForExecution(player.getName().getString()); {
CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName());
targets.add(host);
if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource());
}
} }
catch (CommandSyntaxException e) else
{ {
host = (CarpetScriptHost) retrieveForExecution((String) null); ServerPlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget);
if (player != null)
{
CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName());
targets.add(host);
if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource());
}
} }
} }
if (host.errorSnooper == null) host.setChatErrorSnooper(source); else
return host; {
targets.add(this);
if (this.errorSnooper == null) this.setChatErrorSnooper(source);
}
return targets;
} }
public CarpetScriptHost retrieveForExecution(ServerPlayerEntity target) public CarpetScriptHost retrieveOwnForExecution(ServerCommandSource source) throws CommandSyntaxException
{ {
// allowing only run
if (perUser == (target == null)) return null;
if (!perUser) if (!perUser)
{ {
if (errorSnooper == null) setChatErrorSnooper(this.scriptServer.server.getCommandSource()); if (errorSnooper == null) setChatErrorSnooper(source);
return this; return this;
} }
// user based // user based
CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(target.getName().getString()); ServerPlayerEntity player;
if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(target.getCommandSource()); try
{
player = source.getPlayer();
}
catch (CommandSyntaxException ignored)
{
throw new SimpleCommandExceptionType(new LiteralText("Cannot run player based apps without the player context")).create();
}
CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(player.getEntityName());
if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(source);
return userHost; return userHost;
} }
@@ -645,6 +720,12 @@ public class CarpetScriptHost extends ScriptHost
return Module.listFile(main, resource, "txt", isShared); return Module.listFile(main, resource, "txt", isShared);
} }
public Stream<String> listFolder(String resource, String ext, boolean isShared)
{
if (getName() == null && !isShared) return null; //
return Module.listFolder(main, resource, ext, isShared);
}
public void tick() public void tick()
{ {
@@ -1,8 +1,8 @@
package carpet.script; package carpet.script;
import carpet.CarpetSettings;
import carpet.script.argument.FunctionArgument; import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.BundledModule; import carpet.script.bundled.BundledModule;
import carpet.CarpetSettings;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.script.bundled.FileModule; import carpet.script.bundled.FileModule;
import carpet.script.bundled.Module; import carpet.script.bundled.Module;
@@ -16,19 +16,25 @@ import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import java.io.File; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -116,31 +122,42 @@ public class CarpetScriptServer
}); });
if (CarpetSettings.scriptsAutoload) if (CarpetSettings.scriptsAutoload)
{ {
Messenger.m(server.getCommandSource(), "Auto-loading world scarpet apps");
for (String moduleName: listAvailableModules(false)) for (String moduleName: listAvailableModules(false))
{ {
addScriptHost(server.getCommandSource(), moduleName, null, true, true, false); addScriptHost(server.getCommandSource(), moduleName, null, true, true, false);
} }
} }
CarpetEventServer.Event.START.onTick();
} }
public Module getModule(String name, boolean allowLibraries) public Module getModule(String name, boolean allowLibraries)
{ {
File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile(); try {
File[] listOfFiles = folder.listFiles(); Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
if (listOfFiles != null) Files.createDirectories(folder);
for (File script : listOfFiles) Optional<Path>
scriptPath = Files.list(folder)
.filter(script ->
script.getFileName().toString().equalsIgnoreCase(name+".sc") ||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl"))
).findFirst();
if (scriptPath.isPresent())
return new FileModule(scriptPath.get());
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
{ {
if (script.getName().equalsIgnoreCase(name+".sc")) Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
{ Files.createDirectories(globalFolder);
return new FileModule(script); scriptPath = Files.walk(globalFolder)
} .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
if (allowLibraries && script.getName().equalsIgnoreCase(name+".scl")) (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
{ .findFirst();
return new FileModule(script); if (scriptPath.isPresent())
} return new FileModule(scriptPath.get());
} }
} catch (IOException e) {
CarpetSettings.LOG.error("Exception while loading the app: ", e);
}
for (Module moduleData : bundledModuleData) for (Module moduleData : bundledModuleData)
{ {
if (moduleData.getName().equalsIgnoreCase(name) && (allowLibraries || !moduleData.isLibrary())) if (moduleData.getName().equalsIgnoreCase(name) && (allowLibraries || !moduleData.isLibrary()))
@@ -173,17 +190,23 @@ public class CarpetScriptServer
if (!mi.isLibrary() && !mi.getName().endsWith("_beta")) moduleNames.add(mi.getName()); if (!mi.isLibrary() && !mi.getName().endsWith("_beta")) moduleNames.add(mi.getName());
} }
} }
File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile(); try {
File[] listOfFiles = folder.listFiles(); Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
if (listOfFiles == null) Files.createDirectories(worldScripts);
return moduleNames; Files.list(worldScripts)
for (File script : listOfFiles) .filter(f -> f.toString().endsWith(".sc"))
{ .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
if (script.getName().endsWith(".sc"))
if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT))
{ {
String name = script.getName().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT); Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
moduleNames.add(name); Files.createDirectories(globalScripts);
Files.walk(globalScripts)
.filter(f -> f.toString().endsWith(".sc"))
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
} }
} catch (IOException e) {
CarpetSettings.LOG.error("Exception while searching for apps: ", e);
} }
return moduleNames; return moduleNames;
} }
@@ -198,7 +221,6 @@ public class CarpetScriptServer
public boolean addScriptHost(ServerCommandSource source, String name, Function<ServerCommandSource, Boolean> commandValidator, public boolean addScriptHost(ServerCommandSource source, String name, Function<ServerCommandSource, Boolean> commandValidator,
boolean perPlayer, boolean autoload, boolean isRuleApp) boolean perPlayer, boolean autoload, boolean isRuleApp)
{ {
//TODO add per player modules to support player actions better on a server
if (commandValidator == null) commandValidator = p -> true; if (commandValidator == null) commandValidator = p -> true;
long start = System.nanoTime(); long start = System.nanoTime();
name = name.toLowerCase(Locale.ROOT); name = name.toLowerCase(Locale.ROOT);
@@ -272,6 +294,11 @@ public class CarpetScriptServer
Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app"); Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app");
return false; return false;
} }
if (newHost.isPerUser())
{
// that will provide player hosts right at the startup
newHost.retrieveForExecution(source, null);
}
long end = System.nanoTime(); long end = System.nanoTime();
CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms"); CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms");
return true; return true;
@@ -297,12 +324,23 @@ public class CarpetScriptServer
return true; 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). LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName).
requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)). requires((player) -> modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)).
executes( (c) -> executes( (c) ->
{ {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(),"__command", null, ""); Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger(); return (int)response.readInteger();
}); });
@@ -331,15 +369,15 @@ public class CarpetScriptServer
then(literal(function). then(literal(function).
requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null). requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null).
executes((c) -> { executes((c) -> {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(), function, null, ""); Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, "");
if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString());
return (int) response.readInteger(); return (int) response.readInteger();
}). }).
then(argument("args...", StringArgumentType.greedyString()). then(argument("args...", StringArgumentType.greedyString()).
executes( (c) -> { executes( (c) -> {
Value response = modules.get(hostName).retrieveForExecution(c.getSource()). CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
handleCommandLegacy(c.getSource(), function,null, StringArgumentType.getString(c, "args...")); Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args..."));
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
return (int)response.readInteger(); return (int)response.readInteger();
}))); })));
@@ -370,54 +408,84 @@ public class CarpetScriptServer
return true; return true;
} }
public boolean runas(ServerCommandSource source, String hostname, FunctionValue udf, List<Value> argv) public boolean runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List<Value> argv)
{
return runas(BlockPos.ORIGIN, source, hostname, udf, argv);
}
public boolean runas(BlockPos origin, ServerCommandSource source, String hostname, FunctionValue udf, List<Value> argv)
{ {
CarpetScriptHost host = globalHost; CarpetScriptHost host = globalHost;
try if (hostname != null)
host = modules.get(hostname);
if (host == null) return false;
// dummy call for player apps that reside on the global copy - do not run them, but report as successes.
if (host.isPerUser() && optionalTarget==null) return true;
ServerPlayerEntity target = null;
if (optionalTarget != null)
{ {
if (hostname != null) target = sender.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget);
host = modules.get(hostname).retrieveForExecution(source); if (target == null) return false;
host.callUDF(origin, source, udf, argv);
} }
catch (NullPointerException | InvalidCallbackException npe) int successes = signal(sender, target, hostname, udf, argv, true );
{ return successes >= 0;
return false;
}
return true;
} }
public Boolean runForPlayer(ServerPlayerEntity recipient, String hostname, FunctionValue udf, List<Value> argv) public void runScheduledCall(BlockPos origin, ServerCommandSource source, String hostname, CarpetScriptHost host, FunctionValue udf, List<Value> argv)
{
if (hostname != null && !modules.containsKey(hostname)) // well - scheduled call app got unloaded
return;
try
{
host.callUDF(origin, source, udf, argv);
}
catch (NullPointerException | InvalidCallbackException ignored)
{
}
}
/**
* returns number of successful calls in the host or -1 if failed exceptionally
* @param sender
* @param optionalRecipient
* @param hostname
* @param udf
* @param argv
* @param reportFails - returns -1 when any call failed
* @return
*/
public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, String hostname, FunctionValue udf, List<Value> argv, boolean reportFails)
{ {
CarpetScriptHost host;
ServerCommandSource source;
if (hostname == null) if (hostname == null)
{ {
if (recipient != null) return null; if (optionalRecipient != null) return 0;
host = globalHost; try
source = server.getCommandSource(); {
globalHost.callUDF(BlockPos.ORIGIN, sender, udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
return reportFails?-1:0;
}
return 1;
} }
else String hostRecipient = optionalRecipient == null?null:optionalRecipient.getEntityName();
ServerCommandSource source = (optionalRecipient == null)? sender : optionalRecipient.getCommandSource().withLevel(CarpetSettings.runPermissionLevel);
int successes = 0;
for (CarpetScriptHost host : modules.get(hostname).retrieveForExecution(sender, hostRecipient))
{ {
host = modules.get(hostname).retrieveForExecution(recipient); // getPlayer will always return nonnull cause retrieve for execution only returns hosts for existing players.
if (host == null) // not applicable ServerCommandSource executingSource = host.perUser
return null; ? source.getMinecraftServer().getPlayerManager().getPlayer(host.user).getCommandSource()
source = (recipient == null)? server.getCommandSource() : recipient.getCommandSource(); : source.getMinecraftServer().getCommandSource();
try
{
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
if (reportFails) return -1;
continue;
}
successes ++;
} }
try return successes;
{
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
}
catch (NullPointerException | InvalidCallbackException npe)
{
return false;
}
return true;
} }
public void tick() public void tick()
@@ -431,12 +499,30 @@ public class CarpetScriptServer
public void onClose() public void onClose()
{ {
CarpetEventServer.Event.SHUTDOWN.onTick();
for (ScriptHost host : modules.values()) for (ScriptHost host : modules.values())
{ {
host.onClose(); host.onClose();
} }
} }
public void onPlayerJoin(ServerPlayerEntity player)
{
modules.values().forEach(h ->
{
if (h.isPerUser())
{
try
{
h.retrieveOwnForExecution(player.getCommandSource());
}
catch (CommandSyntaxException ignored)
{
}
}
});
}
static class TransferData static class TransferData
{ {
boolean perUser; boolean perUser;
@@ -9,6 +9,7 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageSource;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -19,7 +20,7 @@ import java.util.Map;
public class EntityEventsGroup public class EntityEventsGroup
{ {
private final Map<Event, Map<String, CarpetEventServer.Callback>> actions; private final Map<Event, Map<Pair<String,String>, CarpetEventServer.Callback>> actions;
private final Entity entity; private final Entity entity;
public EntityEventsGroup(Entity e) public EntityEventsGroup(Entity e)
{ {
@@ -30,37 +31,47 @@ public class EntityEventsGroup
public void onEvent(Event type, Object... args) public void onEvent(Event type, Object... args)
{ {
if (actions.isEmpty()) return; // most of the cases, trying to be nice if (actions.isEmpty()) return; // most of the cases, trying to be nice
Map<String, CarpetEventServer.Callback> actionSet = actions.get(type); Map<Pair<String,String>, CarpetEventServer.Callback> actionSet = actions.get(type);
if (actionSet == null) return; if (actionSet == null) return;
for (Iterator<Map.Entry<String, CarpetEventServer.Callback>> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); ) for (Iterator<Map.Entry<Pair<String,String>, CarpetEventServer.Callback>> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); )
{ {
Map.Entry<String, CarpetEventServer.Callback> action = iterator.next(); Map.Entry<Pair<String,String>, CarpetEventServer.Callback> action = iterator.next();
ScriptHost host = CarpetServer.scriptServer.getHostByName(action.getKey()); Pair<String,String> key = action.getKey();
ScriptHost host = CarpetServer.scriptServer.getHostByName(key.getLeft());
if (host == null) if (host == null)
{ {
iterator.remove(); iterator.remove();
continue; continue;
} }
if (key.getRight() != null)
{
if (entity.getServer().getPlayerManager().getPlayer(key.getRight())==null)
{
iterator.remove();
continue;
}
}
if (!type.call(action.getValue(), entity, args)) if (!type.call(action.getValue(), entity, args))
iterator.remove(); iterator.remove();
} }
if (actionSet.isEmpty()) actions.remove(type); if (actionSet.isEmpty()) actions.remove(type);
} }
public void addEvent(Event type, String hostName, FunctionValue fun, List<Value> extraargs) public void addEvent(Event type, ScriptHost host, FunctionValue fun, List<Value> extraargs)
{ {
Pair<String,String> key = Pair.of(host.getName(), host.user);
if (fun != null) if (fun != null)
{ {
CarpetEventServer.Callback call = type.create(hostName, fun, extraargs); CarpetEventServer.Callback call = type.create(key, fun, extraargs);
if (call == null) if (call == null)
throw new InternalExpressionException("wrong number of arguments for callback, required "+type.argcount); throw new InternalExpressionException("wrong number of arguments for callback, required "+type.argcount);
actions.computeIfAbsent(type, k -> new HashMap<>()).put(hostName, call); actions.computeIfAbsent(type, k -> new HashMap<>()).put(key, call);
} }
else else
{ {
actions.computeIfAbsent(type, k -> new HashMap<>()).remove(hostName); actions.computeIfAbsent(type, k -> new HashMap<>()).remove(key);
if (actions.get(type).isEmpty()) if (actions.get(type).isEmpty())
actions.remove(type); actions.remove(type);
} }
@@ -107,13 +118,13 @@ public class EntityEventsGroup
argcount = args+1; // entity is not extra argcount = args+1; // entity is not extra
byName.put(identifier, this); byName.put(identifier, this);
} }
public CarpetEventServer.Callback create(String hostName, FunctionValue function, List<Value> extraArgs) public CarpetEventServer.Callback create(Pair<String,String> key, FunctionValue function, List<Value> extraArgs)
{ {
if ((function.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argcount) if ((function.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argcount)
{ {
return null; return null;
} }
return new CarpetEventServer.Callback(hostName, function, extraArgs); return new CarpetEventServer.Callback(key.getLeft(), key.getRight(), function, extraArgs);
} }
public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args) public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args)
{ {
+17 -8
View File
@@ -18,13 +18,10 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class ScriptHost public abstract class ScriptHost
@@ -189,7 +186,13 @@ public abstract class ScriptHost
public FunctionValue getAssertFunction(Module module, String name) public FunctionValue getAssertFunction(Module module, String name)
{ {
FunctionValue ret = getFunction(module, name); FunctionValue ret = getFunction(module, name);
if (ret == null) throw new InternalExpressionException("Function "+name+" is not defined yet"); if (ret == null)
{
if (module == main)
throw new InternalExpressionException("Function '"+name+"' is not defined yet");
else
throw new InternalExpressionException("Function '"+name+"' is not defined nor visible by its name in the imported module '"+module.getName()+"'");
}
return ret; return ret;
} }
private FunctionValue getFunction(Module module, String name) private FunctionValue getFunction(Module module, String name)
@@ -318,15 +321,21 @@ public abstract class ScriptHost
ScriptHost oldUserHost = userHosts.get(user); ScriptHost oldUserHost = userHosts.get(user);
if (oldUserHost != null) return oldUserHost; if (oldUserHost != null) return oldUserHost;
ScriptHost userHost = this.duplicate(); ScriptHost userHost = this.duplicate();
userHost.modules.putAll(this.modules);
this.moduleData.forEach((key, value) -> userHost.moduleData.put(key, new ModuleData(key, value)));
// fixing imports
userHost.moduleData.forEach((module, data) -> data.setImportsBasedOn(userHost, this.moduleData.get(data.parent)));
userHost.user = user; userHost.user = user;
this.transferToChild(userHost);
userHosts.put(user, userHost); userHosts.put(user, userHost);
return userHost; return userHost;
} }
protected void transferToChild(ScriptHost host)
{
// adding imports
host.modules.putAll(this.modules);
this.moduleData.forEach((key, value) -> host.moduleData.put(key, new ModuleData(key, value)));
// fixing imports
host.moduleData.forEach((module, data) -> data.setImportsBasedOn(host, this.moduleData.get(data.parent)));
}
public void handleExpressionException(String msg, ExpressionException exc) public void handleExpressionException(String msg, ExpressionException exc)
{ {
System.out.println(msg+": "+exc); System.out.println(msg+": "+exc);
+74 -69
View File
@@ -18,6 +18,7 @@ import carpet.script.argument.FunctionArgument;
import carpet.script.argument.Vector3Argument; import carpet.script.argument.Vector3Argument;
import carpet.script.exception.ExitStatement; import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.utils.FixedCommandSource;
import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.ShapeDispatcher;
import carpet.script.utils.WorldTools; import carpet.script.utils.WorldTools;
import carpet.script.value.EntityValue; import carpet.script.value.EntityValue;
@@ -32,8 +33,8 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.script.value.ValueConversions; import carpet.script.value.ValueConversions;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.google.common.collect.ImmutableMap;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.EquipmentSlot;
@@ -48,9 +49,7 @@ import net.minecraft.network.packet.s2c.play.TitleS2CPacket;
import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action; import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action;
import net.minecraft.particle.ParticleEffect; import net.minecraft.particle.ParticleEffect;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.CommandOutput;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
@@ -63,12 +62,12 @@ import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.DynamicRegistryManager; import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.Registry;
import net.minecraft.world.World; import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -87,19 +86,39 @@ import static java.lang.Math.min;
public class Auxiliary { public class Auxiliary {
public static final String MARKER_STRING = "__scarpet_marker"; 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)); private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
private static final Map<String, String> supportedTypes = ImmutableMap.of(
"raw", ".txt",
"text", ".txt",
"nbt", ".nbt",
"folder", "folder"
);
public static String recognizeResource(Value value) public static String recognizeResource(Value value, boolean isFloder)
{ {
String origfile = value.getString(); String origfile = value.getString();
String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", ""); String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", "");
file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
if (file.isEmpty()) if (file.isEmpty() && !isFloder)
{ {
throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers");
} }
return file; 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 (!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) public static void apply(Expression expression)
{ {
expression.addLazyFunction("sound", -1, (c, t, lv) -> { expression.addLazyFunction("sound", -1, (c, t, lv) -> {
@@ -310,7 +329,7 @@ public class Auxiliary {
{ {
Value nameValue = lv.get(0).evalValue(c); Value nameValue = lv.get(0).evalValue(c);
name = nameValue instanceof NullValue ? "" : nameValue.getString(); name = nameValue instanceof NullValue ? "" : nameValue.getString();
pointLocator = Vector3Argument.findIn(cc, lv, 1, true); pointLocator = Vector3Argument.findIn(cc, lv, 1, true, false);
if (lv.size()>pointLocator.offset) if (lv.size()>pointLocator.offset)
{ {
BlockArgument blockLocator = BlockArgument.findIn(cc, lv, pointLocator.offset, true, true, false); BlockArgument blockLocator = BlockArgument.findIn(cc, lv, pointLocator.offset, true, true, false);
@@ -446,28 +465,29 @@ public class Auxiliary {
{ {
if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments"); if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments");
ServerCommandSource s = ((CarpetContext)c).s; ServerCommandSource s = ((CarpetContext)c).s;
MinecraftServer server = s.getMinecraftServer();
Value res = lv.get(0).evalValue(c); Value res = lv.get(0).evalValue(c);
List<ServerPlayerEntity> targets = null;
if (lv.size() == 2) if (lv.size() == 2)
{ {
ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res); List<Value> playerValues = (res instanceof ListValue)?((ListValue) res).getItems():Collections.singletonList(res);
if (player == null) return LazyValue.NULL; List<ServerPlayerEntity> playerTargets = new ArrayList<>();
s = player.getCommandSource(); playerValues.forEach(pv -> {
ServerPlayerEntity player = EntityValue.getPlayerByValue(server, pv);
if (player == null) throw new InternalExpressionException("Cannot target player "+pv.getString()+" in print");
playerTargets.add(player);
});
targets = playerTargets;
res = lv.get(1).evalValue(c); res = lv.get(1).evalValue(c);
} }
if (res instanceof FormattedTextValue) Text message = (res instanceof FormattedTextValue)?((FormattedTextValue) res).getText():new LiteralText(res.getString());
if (targets == null)
{ {
s.sendFeedback(((FormattedTextValue) res).getText(), false); s.sendFeedback(message, false);
} }
else else
{ {
if (s.getEntity() instanceof PlayerEntity) targets.forEach(p -> p.getCommandSource().sendFeedback(message, false));
{
Messenger.m((PlayerEntity) s.getEntity(), "w " + res.getString());
}
else
{
Messenger.m(s, "w " + res.getString());
}
} }
Value finalRes = res; Value finalRes = res;
return (_c, _t) -> finalRes; // pass through for variables return (_c, _t) -> finalRes; // pass through for variables
@@ -510,7 +530,7 @@ public class Auxiliary {
if (pVal instanceof FormattedTextValue) if (pVal instanceof FormattedTextValue)
title = ((FormattedTextValue) pVal).getText(); title = ((FormattedTextValue) pVal).getText();
else else
title = Text.of(pVal.getString()); title = new LiteralText(pVal.getString());
} }
TitleS2CPacket timesPacket; TitleS2CPacket timesPacket;
if (lv.size() > 3) if (lv.size() > 3)
@@ -544,29 +564,14 @@ public class Auxiliary {
expression.addLazyFunction("run", 1, (c, t, lv) -> { expression.addLazyFunction("run", 1, (c, t, lv) -> {
BlockPos target = ((CarpetContext)c).origin; BlockPos target = ((CarpetContext)c).origin;
Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D); Vec3d posf = new Vec3d((double)target.getX()+0.5D, target.getY(),(double)target.getZ()+0.5D);
ServerCommandSource s = ((CarpetContext)c).s; ServerCommandSource s = ((CarpetContext)c).s;
try try
{ {
Value[] error = {Value.NULL}; Value[] error = {Value.NULL};
List<Value> output = new ArrayList<>(); List<Value> output = new ArrayList<>();
Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute( Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute(
new ServerCommandSource( new FixedCommandSource(s, posf, error, output),
CommandOutput.DUMMY, posf, Vec2f.ZERO, s.getWorld(), CarpetSettings.runPermissionLevel,
s.getName(), s.getDisplayName(), s.getMinecraftServer(), s.getEntity(), true,
(ctx, succ, res) -> { }, EntityAnchorArgumentType.EntityAnchor.FEET)
{
@Override
public void sendError(Text message)
{
error[0] = new FormattedTextValue(message);
}
@Override
public void sendFeedback(Text message, boolean broadcastToOps)
{
output.add(new FormattedTextValue(message));
}
},
lv.get(0).evalValue(c).getString()) lv.get(0).evalValue(c).getString())
); );
Value ret = ListValue.of(retval, ListValue.wrap(output), error[0]); Value ret = ListValue.of(retval, ListValue.wrap(output), error[0]);
@@ -785,23 +790,29 @@ public class Auxiliary {
return (_c, _t) -> res; // pass through for variables return (_c, _t) -> res; // pass through for variables
}); });
expression.addLazyFunction("read_file", 2, (c, t, lv) -> { expression.addLazyFunction("list_files", 2, (c, t, lv) ->
String resource = recognizeResource(lv.get(0).evalValue(c)); {
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
boolean shared = origtype.startsWith("shared_"); Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight());
String type = shared ? origtype.substring(7) : origtype; //len(shared_) if (files == null) return LazyValue.NULL;
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
throw new InternalExpressionException("Unsupported file type: "+origtype); return (cc, tt) -> ret;
});
expression.addLazyFunction("read_file", 2, (c, t, lv) ->
{
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
Value retVal; Value retVal;
if (type.equals("nbt")) if (fdesc.getMiddle().equals("nbt"))
{ {
Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared); Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
if (state == null) return LazyValue.NULL; if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state); retVal = new NBTSerializableValue(state);
} }
else else
{ {
List<String> content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared); List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getRight());
if (content == null) return LazyValue.NULL; if (content == null) return LazyValue.NULL;
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList())); retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
} }
@@ -809,33 +820,26 @@ public class Auxiliary {
}); });
expression.addLazyFunction("delete_file", 2, (c, t, lv) -> { expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
String resource = recognizeResource(lv.get(0).evalValue(c)); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
boolean shared = origtype.startsWith("shared_"); ? LazyValue.TRUE
String type = shared ? origtype.substring(7) : origtype; //len(shared_) : LazyValue.FALSE;
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type);
return success?LazyValue.TRUE:LazyValue.FALSE;
}); });
expression.addLazyFunction("write_file", -1, (c, t, lv) -> { expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments"); if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
String resource = recognizeResource(lv.get(0).evalValue(c)); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
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 (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success; boolean success;
if (type.equals("nbt")) if (fdesc.getMiddle().equals("nbt"))
{ {
Value val = lv.get(2).evalValue(c); Value val = lv.get(2).evalValue(c);
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val ? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString()); : new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag(); Tag tag = tagValue.getTag();
success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared); success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
} }
else else
{ {
@@ -860,7 +864,7 @@ public class Auxiliary {
data.add(lv.get(i).evalValue(c).getString()); data.add(lv.get(i).evalValue(c).getString());
} }
} }
success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data); success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
} }
return success?LazyValue.TRUE:LazyValue.FALSE; return success?LazyValue.TRUE:LazyValue.FALSE;
}); });
@@ -874,7 +878,7 @@ public class Auxiliary {
if (lv.size()>0) if (lv.size()>0)
{ {
c.host.issueDeprecation("load_app_data(...) with arguments"); c.host.issueDeprecation("load_app_data(...) with arguments");
file = recognizeResource(lv.get(0).evalValue(c)); file = recognizeResource(lv.get(0).evalValue(c), false);
if (lv.size() > 1) if (lv.size() > 1)
{ {
shared = lv.get(1).evalValue(c).getBoolean(); shared = lv.get(1).evalValue(c).getBoolean();
@@ -897,7 +901,7 @@ public class Auxiliary {
if (lv.size()>1) if (lv.size()>1)
{ {
c.host.issueDeprecation("store_app_data(...) with more than one argument"); c.host.issueDeprecation("store_app_data(...) with more than one argument");
file = recognizeResource(lv.get(1).evalValue(c)); file = recognizeResource(lv.get(1).evalValue(c), false);
if (lv.size() > 2) if (lv.size() > 2)
{ {
shared = lv.get(2).evalValue(c).getBoolean(); shared = lv.get(2).evalValue(c).getBoolean();
@@ -961,6 +965,7 @@ public class Auxiliary {
{ {
if (lv.size() == 0) if (lv.size() == 0)
throw new InternalExpressionException("'signal' requires at least one argument"); throw new InternalExpressionException("'signal' requires at least one argument");
CarpetContext cc = (CarpetContext)c;
CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer(); CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer();
String eventName = lv.get(0).evalValue(c).getString(); String eventName = lv.get(0).evalValue(c).getString();
// no such event yet // no such event yet
@@ -975,10 +980,10 @@ public class Auxiliary {
args = FunctionValue.resolveArgs(lv.subList(2, lv.size()), c, t); args = FunctionValue.resolveArgs(lv.subList(2, lv.size()), c, t);
} }
} }
int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, (CarpetScriptHost) c.host, player, args); int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, cc, player, args);
if (counts < 0) return LazyValue.NULL; if (counts < 0) return LazyValue.NULL;
Value ret = new NumericValue(counts); Value ret = new NumericValue(counts);
return (cc, tt) -> ret; return (c_, t_) -> ret;
}); });
} }
+30 -13
View File
@@ -23,6 +23,7 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
@@ -34,6 +35,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -183,8 +185,9 @@ public class Entities {
expression.addLazyFunction("entity_list", 1, (c, t, lv) -> expression.addLazyFunction("entity_list", 1, (c, t, lv) ->
{ {
String who = lv.get(0).evalValue(c).getString(); String who = lv.get(0).evalValue(c).getString();
EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who); ServerCommandSource source = ((CarpetContext)c).s;
List<Entity> entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, eDesc.filteringPredicate); EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who, source.getMinecraftServer());
List<Entity> entityList = source.getWorld().getEntitiesByType(eDesc.directType, eDesc.filteringPredicate);
Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList()));
return (_c, _t ) -> retval; return (_c, _t ) -> retval;
}); });
@@ -194,16 +197,26 @@ public class Entities {
if (lv.size()<3) throw new InternalExpressionException("'entity_area' requires entity type, center and range arguments"); if (lv.size()<3) throw new InternalExpressionException("'entity_area' requires entity type, center and range arguments");
String who = lv.get(0).evalValue(c).getString(); String who = lv.get(0).evalValue(c).getString();
CarpetContext cc = (CarpetContext)c; CarpetContext cc = (CarpetContext)c;
Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1); Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1, false, true);
Vec3d center = centerLocator.vec;
if (centerLocator.fromBlock) center.add(0.5, 0.5, 0.5); Box centerBox;
if (centerLocator.entity != null)
{
centerBox = centerLocator.entity.getBoundingBox();
}
else
{
Vec3d center = centerLocator.vec;
if (centerLocator.fromBlock) center.add(0.5, 0.5, 0.5);
centerBox = new Box(center, center);
}
Vector3Argument rangeLocator = Vector3Argument.findIn(cc, lv, centerLocator.offset); Vector3Argument rangeLocator = Vector3Argument.findIn(cc, lv, centerLocator.offset);
if (rangeLocator.fromBlock) if (rangeLocator.fromBlock)
throw new InternalExpressionException("Range of 'entity_area' cannot come from a block argument"); throw new InternalExpressionException("Range of 'entity_area' cannot come from a block argument");
Vec3d range = rangeLocator.vec; Vec3d range = rangeLocator.vec;
Box area = new Box(center, center).expand(range.x, range.y, range.z); Box area = centerBox.expand(range.x, range.y, range.z);
EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who); EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who, cc.s.getMinecraftServer());
List<? extends Entity> entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate); List<? extends Entity> entityList = cc.s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate);
Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList()));
return (_c, _t ) -> retval; return (_c, _t ) -> retval;
}); });
@@ -228,7 +241,9 @@ public class Entities {
Value v = lv.get(0).evalValue(c); Value v = lv.get(0).evalValue(c);
if (!(v instanceof EntityValue)) if (!(v instanceof EntityValue))
throw new InternalExpressionException("First argument to query should be an entity"); throw new InternalExpressionException("First argument to query should be an entity");
String what = lv.get(1).evalValue(c).getString(); String what = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
if (what.equals("tags"))
c.host.issueDeprecation("'tags' for entity querying");
Value retval; Value retval;
if (lv.size()==2) if (lv.size()==2)
retval = ((EntityValue) v).get(what, null); retval = ((EntityValue) v).get(what, null);
@@ -259,11 +274,13 @@ public class Entities {
return (cc, tt) -> v; return (cc, tt) -> v;
}); });
expression.addFunction("entity_types", (lv) -> expression.addLazyFunction("entity_types", -1, (c, t, lv) ->
{ {
if (lv.size() > 1) throw new InternalExpressionException("'entity_types' requires one or no arguments"); if (lv.size() > 1) throw new InternalExpressionException("'entity_types' requires one or no arguments");
String desc = (lv.size() == 1)?lv.get(0).getString():"*"; String desc = (lv.size() == 1)?lv.get(0).evalValue(c).getString():"*";
return EntityValue.getEntityDescriptor(desc).listValue; Value ret = EntityValue.getEntityDescriptor(desc, ((CarpetContext) c).s.getMinecraftServer()).listValue;
return (_c, _t) -> ret;
}); });
expression.addLazyFunction("entity_load_handler", -1, (c, t, lv) -> expression.addLazyFunction("entity_load_handler", -1, (c, t, lv) ->
@@ -275,7 +292,7 @@ public class Entities {
? ((ListValue) entityValue).getItems().stream().map(Value::getString).collect(Collectors.toList()) ? ((ListValue) entityValue).getItems().stream().map(Value::getString).collect(Collectors.toList())
: Collections.singletonList(entityValue.getString()); : Collections.singletonList(entityValue.getString());
Set<EntityType<? extends Entity>> types = new HashSet<>(); Set<EntityType<? extends Entity>> types = new HashSet<>();
descriptors.forEach(s -> types.addAll(EntityValue.getEntityDescriptor(s).typeList)); descriptors.forEach(s -> types.addAll(EntityValue.getEntityDescriptor(s, ((CarpetContext) c).s.getMinecraftServer()).typeList));
FunctionArgument<LazyValue> funArg = FunctionArgument.findIn(c, expression.module, lv, 1, true, false); FunctionArgument<LazyValue> funArg = FunctionArgument.findIn(c, expression.module, lv, 1, true, false);
CarpetEventServer events = ((CarpetScriptHost)c.host).getScriptServer().events; CarpetEventServer events = ((CarpetScriptHost)c.host).getScriptServer().events;
if (funArg.function == null) if (funArg.function == null)
@@ -12,6 +12,7 @@ import carpet.script.value.NullValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.StringValue; import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.command.argument.ItemStackArgument; import net.minecraft.command.argument.ItemStackArgument;
@@ -32,6 +33,7 @@ import net.minecraft.recipe.ShapedRecipe;
import net.minecraft.recipe.ShapelessRecipe; import net.minecraft.recipe.ShapelessRecipe;
import net.minecraft.recipe.SpecialCraftingRecipe; import net.minecraft.recipe.SpecialCraftingRecipe;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.tag.TagManager;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
@@ -40,6 +42,7 @@ import net.minecraft.util.registry.Registry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class Inventories { public class Inventories {
public static void apply(Expression expression) public static void apply(Expression expression)
@@ -60,6 +63,43 @@ public class Inventories {
return (_c, _t) -> res; return (_c, _t) -> res;
}); });
expression.addLazyFunction("item_list", -1, (c, t, lv) ->
{
if (lv.size() == 0)
{
Value res = ListValue.wrap(Registry.ITEM.getIds().stream().map(ValueConversions::of).collect(Collectors.toList()));
return (cc, tt) -> res;
}
CarpetContext cc = (CarpetContext)c;
TagManager tagManager = cc.s.getMinecraftServer().getTagManager();
String tag = lv.get(0).evalValue(c).getString();
net.minecraft.tag.Tag<Item> itemTag = tagManager.getItems().getTag(new Identifier(tag));
if (itemTag == null) return LazyValue.NULL;
Value ret = ListValue.wrap(itemTag.values().stream().map(b -> ValueConversions.of(Registry.ITEM.getId(b))).collect(Collectors.toList()));
return (_c, _t) -> ret;
});
expression.addLazyFunction("item_tags", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
TagManager tagManager = cc.s.getMinecraftServer().getTagManager();
if (lv.size() == 0)
{
Value ret = ListValue.wrap(tagManager.getItems().getTagIds().stream().map(ValueConversions::of).collect(Collectors.toList()));
return (_c, _t) -> ret;
}
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()));
return (_c, _t) -> ret;
}
String tag = lv.get(1).evalValue(c).getString();
net.minecraft.tag.Tag<Item> itemTag = tagManager.getItems().getTag(new Identifier(tag));
if (itemTag == null) return LazyValue.NULL;
return item.isIn(itemTag)?LazyValue.TRUE:LazyValue.FALSE;
});
expression.addLazyFunction("recipe_data", -1, (c, t, lv) -> expression.addLazyFunction("recipe_data", -1, (c, t, lv) ->
{ {
CarpetContext cc = (CarpetContext)c; CarpetContext cc = (CarpetContext)c;
@@ -15,6 +15,9 @@ import carpet.script.value.FormattedTextValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.entity.boss.BossBarManager;
import net.minecraft.entity.boss.CommandBossBar;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.scoreboard.Scoreboard; import net.minecraft.scoreboard.Scoreboard;
import net.minecraft.scoreboard.ServerScoreboard; import net.minecraft.scoreboard.ServerScoreboard;
@@ -23,9 +26,12 @@ import net.minecraft.scoreboard.ScoreboardObjective;
import net.minecraft.scoreboard.ScoreboardPlayerScore; import net.minecraft.scoreboard.ScoreboardPlayerScore;
import net.minecraft.scoreboard.Team; import net.minecraft.scoreboard.Team;
import net.minecraft.scoreboard.AbstractTeam; import net.minecraft.scoreboard.AbstractTeam;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.LiteralText; import net.minecraft.text.LiteralText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.util.Formatting; import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -404,6 +410,131 @@ public class Scoreboards {
} }
return LazyValue.TRUE; return LazyValue.TRUE;
}); });
expression.addLazyFunction("bossbar", -1, (c, t, lv) ->
{
BossBarManager bossBarManager = ((CarpetContext)c).s.getMinecraftServer().getBossBarManager();
if(lv.size() > 3) throw new InternalExpressionException("'bossbar' accepts max three arguments");
if(lv.size() == 0) {
Value ret = ListValue.wrap(bossBarManager.getAll().stream().map(CommandBossBar::getId).map(Identifier::toString).map(StringValue::of).collect(Collectors.toList()));
return (_c, _t) -> ret;
}
String id = lv.get(0).evalValue(c).getString();
Identifier identifier;
try {
identifier = new Identifier(id);
} catch (InvalidIdentifierException invalidIdentifierException) {
return LazyValue.NULL;
}
if(lv.size() == 1) {
if(bossBarManager.get(identifier) != null) return LazyValue.FALSE;
Value ret = StringValue.of(bossBarManager.add(identifier,new LiteralText(id)).getId().toString());
return (_c, _t) -> ret;
}
String property = lv.get(1).evalValue(c).getString();
CommandBossBar bossBar = bossBarManager.get(identifier);
if(bossBar == null) return LazyValue.NULL;
Value propertyValue = (lv.size() == 3)?lv.get(2).evalValue(c):null;
switch (property) {
case "color":
if(propertyValue == null) {
BossBar.Color color = (bossBar).getColor();
if(color == null) return LazyValue.NULL;
return (_c, _t) -> StringValue.of(color.getName());
}
BossBar.Color color = BossBar.Color.byName(propertyValue.getString());
if(color == null) return LazyValue.NULL;
bossBar.setColor(BossBar.Color.byName(propertyValue.getString()));
return LazyValue.TRUE;
case "max":
if(propertyValue == null) return (_c, _t) -> NumericValue.of(bossBar.getMaxValue());
if(!(propertyValue instanceof NumericValue)) throw new InternalExpressionException("'bossbar' requires a number as the value for the property " + property);
bossBar.setMaxValue(((NumericValue) propertyValue).getInt());
return LazyValue.TRUE;
case "name":
if(propertyValue == null) return (_c, _t) -> new FormattedTextValue(bossBar.getName());
if(propertyValue instanceof FormattedTextValue) {
bossBar.setName(((FormattedTextValue) propertyValue).getText());
} else {
bossBar.setName(new LiteralText(propertyValue.getString()));
}
return LazyValue.TRUE;
case "add_player":
if(propertyValue == null) throw new InternalExpressionException("Bossbar property " + property + " can't be queried, add a third parameter");
if(propertyValue instanceof ListValue) {
((ListValue) propertyValue).getItems().forEach((v)->{
ServerPlayerEntity player = EntityValue.getPlayerByValue(((CarpetContext)c).s.getMinecraftServer(),propertyValue);
if(player != null) bossBar.addPlayer(player);
});
return LazyValue.TRUE;
}
ServerPlayerEntity player = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), propertyValue);
if(player != null) {
bossBar.addPlayer(player);
return LazyValue.TRUE;
}
return LazyValue.FALSE;
case "players":
if(propertyValue == null) {
return (_c, _t) -> ListValue.wrap(bossBar.getPlayers().stream().map(EntityValue::new).collect(Collectors.toList()));
}
if(propertyValue instanceof ListValue) {
bossBar.clearPlayers();
((ListValue) propertyValue).getItems().forEach((v) -> {
ServerPlayerEntity p = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), v);
if (p != null) bossBar.addPlayer(p);
});
return LazyValue.TRUE;
}
ServerPlayerEntity p = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), propertyValue);
bossBar.clearPlayers();
if (p != null) {
bossBar.addPlayer(p);
return LazyValue.TRUE;
}
return LazyValue.FALSE;
case "style":
if(propertyValue == null) {
return (_c, _t) -> StringValue.of(bossBar.getOverlay().getName());
}
BossBar.Style style = BossBar.Style.byName(propertyValue.getString());
if(style == null) throw new InternalExpressionException("'" + propertyValue.getString() + "' is not a valid value for property " + property);
bossBar.setOverlay(style);
return LazyValue.TRUE;
case "value":
if(propertyValue == null) return (_c, _t) -> NumericValue.of(bossBar.getValue());
if(!(propertyValue instanceof NumericValue)) throw new InternalExpressionException("'bossbar' requires a number as the value for the property " + property);
bossBar.setValue(((NumericValue) propertyValue).getInt());
return LazyValue.TRUE;
case "visible":
if(propertyValue == null) return (_c, _t) -> new NumericValue(bossBar.isVisible());
bossBar.setVisible(propertyValue.getBoolean());
return LazyValue.TRUE;
case "remove":
bossBarManager.remove(bossBar);
return LazyValue.TRUE;
default:
throw new InternalExpressionException("Unknown bossbar property " + property);
}
});
} }
} }
@@ -6,6 +6,7 @@ import carpet.fakes.BiomeArrayInterface;
import carpet.fakes.ChunkGeneratorInterface; import carpet.fakes.ChunkGeneratorInterface;
import carpet.fakes.ChunkTicketManagerInterface; import carpet.fakes.ChunkTicketManagerInterface;
import carpet.fakes.ServerChunkManagerInterface; import carpet.fakes.ServerChunkManagerInterface;
import carpet.fakes.ServerWorldInterface;
import carpet.fakes.SpawnHelperInnerInterface; import carpet.fakes.SpawnHelperInnerInterface;
import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface;
import carpet.helpers.FeatureGenerator; import carpet.helpers.FeatureGenerator;
@@ -58,6 +59,7 @@ import net.minecraft.item.SwordItem;
import net.minecraft.item.TridentItem; import net.minecraft.item.TridentItem;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicket; import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketType; import net.minecraft.server.world.ChunkTicketType;
@@ -89,6 +91,7 @@ import net.minecraft.world.gen.ChunkRandom;
import net.minecraft.world.gen.chunk.ChunkGenerator; import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.feature.ConfiguredStructureFeature; import net.minecraft.world.gen.feature.ConfiguredStructureFeature;
import net.minecraft.world.gen.feature.StructureFeature; import net.minecraft.world.gen.feature.StructureFeature;
import net.minecraft.world.level.ServerWorldProperties;
import net.minecraft.world.poi.PointOfInterest; import net.minecraft.world.poi.PointOfInterest;
import net.minecraft.world.poi.PointOfInterestStorage; import net.minecraft.world.poi.PointOfInterestStorage;
import net.minecraft.world.poi.PointOfInterestType; import net.minecraft.world.poi.PointOfInterestType;
@@ -413,6 +416,67 @@ public class WorldAccess {
}); });
expression.addLazyFunction("weather",-1,(c, t, lv) -> {
ServerWorld world = ((CarpetContext) c).s.getWorld();
if(lv.size()==0)//cos it can thunder when raining or when clear.
{
Value ret = new StringValue(world.isThundering() ? "thunder" : (world.isRaining() ? "rain" : "clear"));
return (_c, _t) -> ret;
}
Value weather = lv.get(0).evalValue(c);
ServerWorldProperties worldProperties = ((ServerWorldInterface) world).getWorldPropertiesCM();
if(lv.size()==1)
{
int ticks;
switch (weather.getString().toLowerCase(Locale.ROOT)) {
case "clear":
ticks = worldProperties.getClearWeatherTime();
break;
case "rain":
ticks = world.isRaining()? worldProperties.getRainTime():0;//cos if not it gives 1 for some reason
break;
case "thunder":
ticks = world.isThundering()? worldProperties.getThunderTime():0;//same dealio here
break;
default:
throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'");
}
Value ret = new NumericValue(ticks);
return (_c,_t) -> ret;
}
if(lv.size()==2)
{
int ticks = NumericValue.asNumber(lv.get(1).evalValue(c), "tick_time in 'weather'").getInt();
switch (weather.getString().toLowerCase(Locale.ROOT))
{
case "clear":
world.setWeather(ticks,0,false,false);
break;
case "rain":
world.setWeather(0,ticks,true,false);
break;
case "thunder":
world.setWeather(
0,
ticks,//this is used to set thunder time, idk why...
true,
true
);
break;
default:
throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'");
}
Value ret = NumericValue.of(ticks);
return (_c, _t) -> ret;
}
throw new InternalExpressionException("'weather' requires 0, 1 or 2 arguments");
});
expression.addLazyFunction("pos", 1, (c, t, lv) -> expression.addLazyFunction("pos", 1, (c, t, lv) ->
{ {
Value arg = lv.get(0).evalValue(c); Value arg = lv.get(0).evalValue(c);
@@ -713,6 +777,21 @@ public class WorldAccess {
} }
args = ((ListValue) args.get(0)).getItems(); args = ((ListValue) args.get(0)).getItems();
} }
else if (args.get(0) instanceof MapValue)
{
if (args.size() == 2)
{
Value dataValue = NBTSerializableValue.fromValue( args.get(1));
if (dataValue instanceof NBTSerializableValue)
{
data = ((NBTSerializableValue) dataValue).getCompoundTag();
}
}
Map<Value, Value> state = ((MapValue) args.get(0)).getMap();
List<Value> mapargs = new ArrayList<>();
state.forEach( (k, v) -> {mapargs.add(k); mapargs.add(v);});
args = mapargs;
}
else else
{ {
if ((args.size() & 1) == 1) if ((args.size() & 1) == 1)
@@ -748,7 +827,7 @@ public class WorldAccess {
{ {
Clearable.clear(world.getBlockEntity(targetPos)); Clearable.clear(world.getBlockEntity(targetPos));
boolean success = world.setBlockState(targetPos, finalSourceBlockState, 2); boolean success = world.setBlockState(targetPos, finalSourceBlockState, 2);
if (success && finalData != null) if (finalData != null)
{ {
BlockEntity be = world.getBlockEntity(targetPos); BlockEntity be = world.getBlockEntity(targetPos);
if (be != null) if (be != null)
@@ -759,6 +838,7 @@ public class WorldAccess {
destTag.putInt("z", targetPos.getZ()); destTag.putInt("z", targetPos.getZ());
be.fromTag(finalSourceBlockState, destTag); be.fromTag(finalSourceBlockState, destTag);
be.markDirty(); be.markDirty();
success = true;
} }
} }
result[0] = success; result[0] = success;
@@ -4,9 +4,11 @@ import carpet.script.CarpetContext;
import carpet.script.LazyValue; import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue; import carpet.script.value.BlockValue;
import carpet.script.value.EntityValue;
import carpet.script.value.ListValue; import carpet.script.value.ListValue;
import carpet.script.value.NumericValue; import carpet.script.value.NumericValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import java.util.List; import java.util.List;
@@ -16,7 +18,8 @@ public class Vector3Argument extends Argument
public Vec3d vec; public Vec3d vec;
public final double yaw; public final double yaw;
public final double pitch; public final double pitch;
public boolean fromBlock; public boolean fromBlock = false;
public Entity entity = null;
private Vector3Argument(Vec3d v, int o) private Vector3Argument(Vec3d v, int o)
{ {
super(o); super(o);
@@ -38,13 +41,19 @@ public class Vector3Argument extends Argument
return this; return this;
} }
private Vector3Argument withEntity(Entity e)
{
entity = e;
return this;
}
public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset) public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset)
{ {
return findIn(c,params, offset, false); return findIn(c,params, offset, false, false);
} }
public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset, boolean optionalDirection) public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset, boolean optionalDirection, boolean optionalEntity)
{ {
try try
{ {
@@ -54,6 +63,11 @@ public class Vector3Argument extends Argument
// pos + 0.5v // pos + 0.5v
return (new Vector3Argument(Vec3d.ofCenter(((BlockValue) v1).getPos()), 1+offset)).fromBlock(); return (new Vector3Argument(Vec3d.ofCenter(((BlockValue) v1).getPos()), 1+offset)).fromBlock();
} }
if (optionalEntity && v1 instanceof EntityValue)
{
Entity e = ((EntityValue) v1).getEntity();
return new Vector3Argument(e.getPos(), 1+offset).withEntity(e);
}
if (v1 instanceof ListValue) if (v1 instanceof ListValue)
{ {
List<Value> args = ((ListValue) v1).getItems(); List<Value> args = ((ListValue) v1).getItems();
@@ -1,5 +1,7 @@
package carpet.script.bundled; package carpet.script.bundled;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.PositionTracker; import net.minecraft.nbt.PositionTracker;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagReaders; import net.minecraft.nbt.TagReaders;
@@ -16,23 +18,25 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Stream;
public class FileModule extends Module public class FileModule extends Module
{ {
private String name; private String name;
private String code; private String code;
private boolean library; private boolean library;
public FileModule(File sourceFile) public FileModule(Path sourcePath)
{ {
library = sourceFile.getName().endsWith(".scl"); library = sourcePath.getFileName().toString().endsWith(".scl");
try try
{ {
name = sourceFile.getName().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT); name = sourcePath.getFileName().toString().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT);
code = new String(Files.readAllBytes(sourceFile.toPath()), StandardCharsets.UTF_8); code = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8);
} }
catch ( IOException e) catch ( IOException e)
{ {
@@ -59,25 +63,40 @@ public class FileModule extends Module
} }
//copied private method from net.minecraft.nbt.NbtIo.read() //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) public static Tag read(File file)
{ {
try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file))) try
{ {
byte byte_1 = dataInput_1.readByte(); return NbtIo.readCompressed(file);
if (byte_1 == 0) }
catch (IOException e)
{
try
{ {
return NbtIo.read(file);
}
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; 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 //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
@@ -86,18 +105,32 @@ public class FileModule extends Module
File file_2 = new File(file.getAbsolutePath() + "_tmp"); File file_2 = new File(file.getAbsolutePath() + "_tmp");
if (file_2.exists()) file_2.delete(); if (file_2.exists()) file_2.delete();
try(DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2))) if (tag_1 instanceof CompoundTag)
{ {
dataOutputStream_1.writeByte(tag_1.getType()); try
if (tag_1.getType() != 0)
{ {
dataOutputStream_1.writeUTF(""); NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
tag_1.write(dataOutputStream_1); }
catch (IOException e)
{
return false;
} }
} }
catch (IOException e) else
{ {
return false; 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.delete();
if (!file.exists()) file_2.renameTo(file); if (!file.exists()) file_2.renameTo(file);
@@ -143,4 +176,18 @@ public class FileModule extends Module
return null; 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;
}
}
} }
+38 -24
View File
@@ -3,10 +3,12 @@ package carpet.script.bundled;
import carpet.CarpetServer; import carpet.CarpetServer;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import org.apache.commons.io.FilenameUtils;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
public abstract class Module public abstract class Module
{ {
@@ -19,61 +21,73 @@ public abstract class Module
{ {
if (isShared) if (isShared)
{ {
return "shared/"+file; return file.isEmpty()?"shared":"shared/"+file;
} }
else if (module != null && module.getName() != null) // appdata if (module != null && module.getName() != null) // appdata
{ {
return module.getName()+".data"+(file==null?"":"/"+file); return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
}
else
{
throw new RuntimeException("Invalid file descriptor: "+file);
} }
throw new RuntimeException("Invalid file descriptor: "+file);
} }
public static Tag getData(Module module, String file, boolean isShared) public static Tag getData(Module module, String file, boolean isShared)
{ {
File dataFile = resolveResource(module, file, "nbt", isShared); Path dataFile = resolveResource(module, file, "nbt", isShared);
if (dataFile == null) return null; if (dataFile == null) return null;
if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null; if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
synchronized (writeIOSync) { return FileModule.read(dataFile); } synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
} }
public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
{ {
File dataFile = resolveResource(module, file, "nbt", isShared); Path dataFile = resolveResource(module, file, "nbt", isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile); } synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
} }
public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message) public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message)
{ {
File dataFile = resolveResource(module, resourceName, "txt", isShared); Path dataFile = resolveResource(module, resourceName, "txt", isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); } synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), type.equals("text"), message); }
} }
public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared) public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared)
{ {
File dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
synchronized (writeIOSync) { return dataFile.delete(); } synchronized (writeIOSync) { return dataFile.toFile().delete(); }
} }
public static List<String> listFile(Module module, String resourceName, String ext, boolean isShared) public static List<String> listFile(Module module, String resourceName, String ext, boolean isShared)
{ {
File dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return null; if (dataFile == null) return null;
if (!dataFile.exists()) return null; if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } synchronized (writeIOSync) { return FileModule.listFileContent(dataFile.toFile()); }
} }
private static File resolveResource(Module module, String resourceName, String ext, boolean isShared) public static Stream<String> listFolder(Module module, String resourceName, String ext, boolean isShared)
{
Path dir = resolveResource(module, resourceName, null, isShared);
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; if (!isShared && (module == null || module.getName() == null)) return null;
return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+"."+ext).toFile(); return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":"."+ext));
} }
@Override @Override
@@ -63,6 +63,7 @@ import net.minecraft.command.argument.RotationArgumentType;
import net.minecraft.command.argument.ScoreHolderArgumentType; import net.minecraft.command.argument.ScoreHolderArgumentType;
import net.minecraft.command.argument.ScoreboardSlotArgumentType; import net.minecraft.command.argument.ScoreboardSlotArgumentType;
import net.minecraft.command.argument.SwizzleArgumentType; import net.minecraft.command.argument.SwizzleArgumentType;
import net.minecraft.command.argument.TeamArgumentType;
import net.minecraft.command.argument.TimeArgumentType; import net.minecraft.command.argument.TimeArgumentType;
import net.minecraft.command.argument.UuidArgumentType; import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.command.argument.Vec2ArgumentType; import net.minecraft.command.argument.Vec2ArgumentType;
@@ -130,7 +131,6 @@ public abstract class CommandArgument
new VanillaUnconfigurableArgument( "blockpredicate", BlockPredicateArgumentType::blockPredicate, new VanillaUnconfigurableArgument( "blockpredicate", BlockPredicateArgumentType::blockPredicate,
(c, p) -> ValueConversions.ofBlockPredicate(c.getSource().getMinecraftServer().getTagManager(), BlockPredicateArgumentType.getBlockPredicate(c, p)), false (c, p) -> ValueConversions.ofBlockPredicate(c.getSource().getMinecraftServer().getTagManager(), BlockPredicateArgumentType.getBlockPredicate(c, p)), false
), ),
// block_predicate todo - not sure about the returned format. Needs to match block tags used in the API (future)
new VanillaUnconfigurableArgument("teamcolor", ColorArgumentType::color, new VanillaUnconfigurableArgument("teamcolor", ColorArgumentType::color,
(c, p) -> { (c, p) -> {
Formatting format = ColorArgumentType.getColor(c, p); Formatting format = ColorArgumentType.getColor(c, p);
@@ -165,7 +165,6 @@ public abstract class CommandArgument
(c, p) -> ValueConversions.of(Registry.ENCHANTMENT.getId(ItemEnchantmentArgumentType.getEnchantment(c, p))), false (c, p) -> ValueConversions.of(Registry.ENCHANTMENT.getId(ItemEnchantmentArgumentType.getEnchantment(c, p))), false
), ),
// item_predicate ?? //same as item but accepts tags, not sure right now // item_predicate ?? //same as item but accepts tags, not sure right now
// slot // item_slot
new SlotArgument(), new SlotArgument(),
new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack, new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack,
(c, p) -> ValueConversions.of(ItemStackArgumentType.getItemStackArgument(c, p).createStack(1, false)), false (c, p) -> ValueConversions.of(ItemStackArgumentType.getItemStackArgument(c, p).createStack(1, false)), false
@@ -241,7 +240,9 @@ public abstract class CommandArgument
new VanillaUnconfigurableArgument("swizzle", SwizzleArgumentType::swizzle, new VanillaUnconfigurableArgument("swizzle", SwizzleArgumentType::swizzle,
(c, p) -> StringValue.of(SwizzleArgumentType.getSwizzle(c, p).stream().map(Direction.Axis::asString).collect(Collectors.joining())), true (c, p) -> StringValue.of(SwizzleArgumentType.getSwizzle(c, p).stream().map(Direction.Axis::asString).collect(Collectors.joining())), true
), ),
// team new VanillaUnconfigurableArgument("team", TeamArgumentType::team,
(c, p) -> StringValue.of(TeamArgumentType.getTeam(c, p).getName()), false
),
new VanillaUnconfigurableArgument("time", TimeArgumentType::time, new VanillaUnconfigurableArgument("time", TimeArgumentType::time,
(c, p) -> new NumericValue(IntegerArgumentType.getInteger(c, p)), false (c, p) -> new NumericValue(IntegerArgumentType.getInteger(c, p)), false
), ),
@@ -437,7 +438,7 @@ public abstract class CommandArgument
if (config.containsKey("options")) if (config.containsKey("options"))
{ {
Value optionsValue = config.get("options"); Value optionsValue = config.get("options");
if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom sting type requires options passed as a list"); if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom string type requires options passed as a list");
validOptions = ((ListValue) optionsValue).getItems().stream() validOptions = ((ListValue) optionsValue).getItems().stream()
.map(v -> caseSensitive?v.getString():(v.getString().toLowerCase(Locale.ROOT))) .map(v -> caseSensitive?v.getString():(v.getString().toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@@ -135,23 +135,25 @@ public class DataStructures {
expression.addFunction("range", (lv) -> expression.addFunction("range", (lv) ->
{ {
long from = 0; NumericValue from = Value.ZERO;
long to = 0; NumericValue to;
long step = 1; NumericValue step = Value.ONE;
int argsize = lv.size(); int argsize = lv.size();
if (argsize == 0 || argsize > 3) if (argsize == 0 || argsize > 3)
throw new InternalExpressionException("'range' accepts from 1 to 3 arguments, not "+argsize); throw new InternalExpressionException("'range' accepts from 1 to 3 arguments, not "+argsize);
to = NumericValue.asNumber(lv.get(0)).getLong(); to = NumericValue.asNumber(lv.get(0));
if (lv.size() > 1) if (lv.size() > 1)
{ {
from = to; from = to;
to = NumericValue.asNumber(lv.get(1)).getLong(); to = NumericValue.asNumber(lv.get(1));
if (lv.size() > 2) if (lv.size() > 2)
{ {
step = NumericValue.asNumber(lv.get(2)).getLong(); step = NumericValue.asNumber(lv.get(2));
} }
} }
return LazyListValue.range(from, to, step); return (from.isInteger() && to.isInteger() && step.isInteger())
? LazyListValue.rangeLong(from.getLong(), to.getLong(), step.getLong())
: LazyListValue.rangeDouble(from.getDouble(), to.getDouble(), step.getDouble());
}); });
expression.addLazyFunction("m", -1, (c, t, llv) -> expression.addLazyFunction("m", -1, (c, t, llv) ->
@@ -0,0 +1,206 @@
package carpet.script.utils;
import carpet.CarpetSettings;
import carpet.script.value.FormattedTextValue;
import carpet.script.value.Value;
import com.mojang.brigadier.ResultConsumer;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.CommandOutput;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import java.util.List;
import java.util.function.BinaryOperator;
public class FixedCommandSource extends ServerCommandSource
{
private Vec3d position;
private ServerWorld world;
private final String simpleName;
private final Text name;
private final MinecraftServer server;
private Entity entity;
private ResultConsumer<ServerCommandSource> resultConsumer;
private EntityAnchorArgumentType.EntityAnchor entityAnchor;
private Vec2f rotation;
private final Value[] error;
private final List<Value> chatOutput;
public FixedCommandSource(ServerCommandSource original, Vec3d pos, Value[] error, List<Value> chatOutput)
{
super(CommandOutput.DUMMY, pos, Vec2f.ZERO, original.getWorld(), CarpetSettings.runPermissionLevel,
original.getName(), original.getDisplayName(), original.getMinecraftServer(), original.getEntity(), true,
(ctx, succ, res) -> { }, EntityAnchorArgumentType.EntityAnchor.FEET);
position = pos;
world = original.getWorld();
simpleName = original.getName();
name = original.getDisplayName();
server = original.getMinecraftServer();
entity = original.getEntity();
resultConsumer = (ctx, succ, res) -> { };
entityAnchor = original.getEntityAnchor();
rotation = Vec2f.ZERO;
this.error = error;
this.chatOutput = chatOutput;
}
@Override
public ServerCommandSource withEntity(Entity entity)
{
this.entity = entity;
return this;
}
@Override
public ServerCommandSource withPosition(Vec3d position)
{
this.position = position;
return this;
}
@Override
public ServerCommandSource withRotation(Vec2f rotation)
{
this.rotation = rotation;
return this;
}
@Override
public ServerCommandSource withConsumer(ResultConsumer<ServerCommandSource> consumer)
{
this.resultConsumer = consumer;
return this;
}
@Override
public ServerCommandSource mergeConsumers(ResultConsumer<ServerCommandSource> consumer, BinaryOperator<ResultConsumer<ServerCommandSource>> binaryOperator)
{
return this.withConsumer(binaryOperator.apply(this.resultConsumer, consumer));
}
@Override
public ServerCommandSource withSilent()
{
return this;
}
@Override
public ServerCommandSource withLevel(int level)
{
return this;
}
@Override
public ServerCommandSource withMaxLevel(int level)
{
return this;
}
@Override
public ServerCommandSource withEntityAnchor(EntityAnchorArgumentType.EntityAnchor anchor)
{
entityAnchor = anchor;
return this;
}
@Override
public ServerCommandSource withWorld(ServerWorld world)
{
this.world = world;
return this;
}
@Override
public Text getDisplayName()
{
return name;
}
@Override
public String getName()
{
return simpleName;
}
@Override
public Vec3d getPosition()
{
return position;
}
@Override
public ServerWorld getWorld()
{
return world;
}
@Override
public Entity getEntity()
{
return entity;
}
@Override
public Entity getEntityOrThrow() throws CommandSyntaxException
{
if (entity == null) {
throw REQUIRES_ENTITY_EXCEPTION.create();
} else {
return entity;
}
}
@Override
public ServerPlayerEntity getPlayer() throws CommandSyntaxException
{
if (!(this.entity instanceof ServerPlayerEntity)) {
throw REQUIRES_PLAYER_EXCEPTION.create();
} else {
return (ServerPlayerEntity)this.entity;
}
}
@Override
public Vec2f getRotation()
{
return rotation;
}
@Override
public MinecraftServer getMinecraftServer()
{
return server;
}
@Override
public EntityAnchorArgumentType.EntityAnchor getEntityAnchor()
{
return entityAnchor;
}
@Override
public void onCommandComplete(CommandContext<ServerCommandSource> context, boolean success, int result)
{
}
@Override
public void sendError(Text message)
{
error[0] = new FormattedTextValue(message);
}
@Override
public void sendFeedback(Text message, boolean broadcastToOps)
{
chatOutput.add(new FormattedTextValue(message));
}
}
@@ -1,6 +1,7 @@
package carpet.script.utils; package carpet.script.utils;
import carpet.CarpetServer; import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.script.CarpetContext; import carpet.script.CarpetContext;
import carpet.script.CarpetScriptHost; import carpet.script.CarpetScriptHost;
import carpet.script.value.ListValue; import carpet.script.value.ListValue;
@@ -11,7 +12,9 @@ import carpet.script.value.Value;
import carpet.settings.ParsedRule; import carpet.settings.ParsedRule;
import carpet.settings.SettingsManager; import carpet.settings.SettingsManager;
import com.sun.management.OperatingSystemMXBean; import com.sun.management.OperatingSystemMXBean;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import net.minecraft.world.GameRules;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.nio.file.Path; import java.nio.file.Path;
@@ -80,6 +83,7 @@ public class SystemInfo {
} }
return whitelist; return whitelist;
}); });
put("server_dev_environment", c-> new NumericValue(FabricLoader.getInstance().isDevelopmentEnvironment()));
put("java_max_memory", c -> new NumericValue(Runtime.getRuntime().maxMemory())); put("java_max_memory", c -> new NumericValue(Runtime.getRuntime().maxMemory()));
put("java_allocated_memory", c -> new NumericValue(Runtime.getRuntime().totalMemory())); put("java_allocated_memory", c -> new NumericValue(Runtime.getRuntime().totalMemory()));
@@ -115,7 +119,7 @@ public class SystemInfo {
CarpetServer.extensions.forEach(e -> { CarpetServer.extensions.forEach(e -> {
SettingsManager manager = e.customSettingsManager(); SettingsManager manager = e.customSettingsManager();
if (manager == null) return; if (manager == null) return;
Collection<ParsedRule<?>> extensionRules = manager.getRules(); Collection<ParsedRule<?>> extensionRules = manager.getRules();
extensionRules.forEach(rule -> { extensionRules.forEach(rule -> {
carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString())); carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString()));
@@ -123,14 +127,18 @@ public class SystemInfo {
}); });
return carpetRules; return carpetRules;
}); });
put("world_gamerules", c->{
Map<Value, Value> rules = new HashMap<>();
final GameRules gameRules = c.s.getWorld().getGameRules();
GameRules.accept(new GameRules.Visitor() {
@Override
public <T extends GameRules.Rule<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) {
rules.put(StringValue.of(key.getName()), StringValue.of(gameRules.get(key).toString()));
}
});
return MapValue.wrap(rules);
});
put("scarpet_version", c -> StringValue.of(CarpetSettings.carpetVersion));
}}; }};
public static Value get(String what, CarpetContext cc) public static Value get(String what, CarpetContext cc)
@@ -9,6 +9,7 @@ import carpet.fakes.MobEntityInterface;
import carpet.fakes.HungerManagerInterface; import carpet.fakes.HungerManagerInterface;
import carpet.fakes.ServerPlayerInteractionManagerInterface; import carpet.fakes.ServerPlayerInteractionManagerInterface;
import carpet.helpers.Tracer; import carpet.helpers.Tracer;
import carpet.network.ServerNetworkHandler;
import carpet.patches.EntityPlayerMPFake; import carpet.patches.EntityPlayerMPFake;
import carpet.script.CarpetContext; import carpet.script.CarpetContext;
import carpet.script.EntityEventsGroup; import carpet.script.EntityEventsGroup;
@@ -29,6 +30,7 @@ import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
import net.minecraft.network.packet.s2c.play.ExperienceBarUpdateS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.command.EntitySelector; import net.minecraft.command.EntitySelector;
import net.minecraft.command.EntitySelectorReader; import net.minecraft.command.EntitySelectorReader;
@@ -221,10 +223,28 @@ public class EntityValue extends Value
return entity.hashCode(); return entity.hashCode();
} }
public static EntityClassDescriptor getEntityDescriptor(String who) public static EntityClassDescriptor getEntityDescriptor(String who, MinecraftServer server)
{ {
EntityClassDescriptor eDesc = EntityClassDescriptor.byName.get(who); EntityClassDescriptor eDesc = EntityClassDescriptor.byName.get(who);
if (eDesc == null) throw new InternalExpressionException(who+" is not a valid entity descriptor"); if (eDesc == null)
{
boolean positive = true;
if (who.startsWith("!"))
{
positive = false;
who = who.substring(1);
}
net.minecraft.tag.Tag<EntityType<?>> eTag = server.getTagManager().getEntityTypes().getTag(new Identifier(who));
if (eTag == null) throw new InternalExpressionException(who+" is not a valid entity descriptor");
if (positive)
{
return new EntityClassDescriptor(null, e -> eTag.contains(e.getType()) && e.isAlive(), eTag.values().stream());
}
else
{
return new EntityClassDescriptor(null, e -> !eTag.contains(e.getType()) && e.isAlive(), Registry.ENTITY_TYPE.stream().filter(et -> !eTag.contains(et)));
}
}
return eDesc; return eDesc;
//TODO add more here like search by tags, or type //TODO add more here like search by tags, or type
//if (who.startsWith('tag:')) //if (who.startsWith('tag:'))
@@ -399,8 +419,21 @@ public class EntityValue extends Value
put("passengers", (e, a) -> ListValue.wrap(e.getPassengerList().stream().map(EntityValue::new).collect(Collectors.toList()))); put("passengers", (e, a) -> ListValue.wrap(e.getPassengerList().stream().map(EntityValue::new).collect(Collectors.toList())));
put("mount", (e, a) -> (e.getVehicle()!=null)?new EntityValue(e.getVehicle()):Value.NULL); put("mount", (e, a) -> (e.getVehicle()!=null)?new EntityValue(e.getVehicle()):Value.NULL);
put("unmountable", (e, a) -> new NumericValue(((EntityInterface)e).isPermanentVehicle())); put("unmountable", (e, a) -> new NumericValue(((EntityInterface)e).isPermanentVehicle()));
// deprecated
put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); 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())));
// deprecated
put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString())));
put("has_scoreboard_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString())));
put("has_entity_tag", (e, a) -> {
net.minecraft.tag.Tag<EntityType<?>> tag = e.getServer().getTagManager().getEntityTypes().getTag(new Identifier(a.getString()));
if (tag == null) return Value.NULL;
return new NumericValue(e.getType().isIn(tag));
});
put("yaw", (e, a)-> new NumericValue(e.yaw)); put("yaw", (e, a)-> new NumericValue(e.yaw));
put("head_yaw", (e, a)-> { put("head_yaw", (e, a)-> {
if (e instanceof LivingEntity) if (e instanceof LivingEntity)
@@ -498,6 +531,31 @@ public class EntityValue extends Value
return Value.NULL; return Value.NULL;
}); });
put("absorption",(e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getAbsorptionAmount());
return Value.NULL;
});
put("xp",(e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).totalExperience);
return Value.NULL;
});
put("xp_level", (e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).experienceLevel);
return Value.NULL;
});
put("xp_progress", (e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).experienceProgress);
return Value.NULL;
});
put("score", (e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getScore());
return Value.NULL;
});
put("jumping", (e, a) -> { put("jumping", (e, a) -> {
if (e instanceof LivingEntity) if (e instanceof LivingEntity)
{ {
@@ -579,6 +637,14 @@ public class EntityValue extends Value
return Value.NULL; return Value.NULL;
}); });
put("client_brand", (e, a) -> {
if (e instanceof ServerPlayerEntity)
{
return StringValue.of(ServerNetworkHandler.getPlayerStatus((ServerPlayerEntity) e));
}
return Value.NULL;
});
put("team", (e, a) -> e.getScoreboardTeam()==null?Value.NULL:new StringValue(e.getScoreboardTeam().getName())); put("team", (e, a) -> e.getScoreboardTeam()==null?Value.NULL:new StringValue(e.getScoreboardTeam().getName()));
put("ping", (e, a) -> { put("ping", (e, a) -> {
@@ -1319,11 +1385,36 @@ public class EntityValue extends Value
if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().addExhaustion((int) NumericValue.asNumber(v).getLong()); if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().addExhaustion((int) NumericValue.asNumber(v).getLong());
}); });
put("saturation", (e, v)-> { put("absorption", (e, v) -> {
if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong()); if (e instanceof PlayerEntity) ((PlayerEntity) e).setAbsorptionAmount((float) NumericValue.asNumber(v, "absorbtion").getLong());
}); });
put("air", (e, v) -> e.setAir(NumericValue.asNumber(v).getInt())); put("add_xp", (e, v) -> {
if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperience(NumericValue.asNumber(v, "add_xp").getInt());
});
put("xp_level", (e, v) -> {
if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperienceLevels(NumericValue.asNumber(v, "xp_level").getInt()-((PlayerEntity) e).experienceLevel);
});
put("xp_progress", (e, v) -> {
if (e instanceof ServerPlayerEntity)
{
ServerPlayerEntity p = (ServerPlayerEntity) e;
p.experienceProgress = NumericValue.asNumber(v, "xp_progress").getFloat();
p.networkHandler.sendPacket(new ExperienceBarUpdateS2CPacket(p.experienceProgress, p.totalExperience, p.experienceLevel));
}
});
put("xp_score", (e, v) -> {
if (e instanceof PlayerEntity) ((PlayerEntity) e).setScore(NumericValue.asNumber(v, "xp_score").getInt());
});
put("saturation", (e, v)-> {
if(e instanceof PlayerEntity) ((HungerManagerInterface) ((PlayerEntity) e).getHungerManager()).setSaturationCM(NumericValue.asNumber(v, "saturation").getFloat());
});
put("air", (e, v) -> e.setAir(NumericValue.asNumber(v, "air").getInt()));
put("breaking_progress", (e, a) -> { put("breaking_progress", (e, a) -> {
if (e instanceof ServerPlayerEntity) if (e instanceof ServerPlayerEntity)
@@ -1372,7 +1463,7 @@ public class EntityValue extends Value
EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName); EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName);
if (event == null) if (event == null)
throw new InternalExpressionException("Unknown entity event: " + eventName); throw new InternalExpressionException("Unknown entity event: " + eventName);
((EntityInterface)entity).getEventContainer().addEvent(event, cc.host.getName(), fun, args); ((EntityInterface)entity).getEventContainer().addEvent(event, cc.host, fun, args);
} }
@Override @Override
@@ -3,6 +3,7 @@ package carpet.script.value;
import net.minecraft.nbt.StringTag; import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.text.BaseText; import net.minecraft.text.BaseText;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text; import net.minecraft.text.Text;
public class FormattedTextValue extends StringValue public class FormattedTextValue extends StringValue
@@ -14,6 +15,33 @@ public class FormattedTextValue extends StringValue
this.text = text; this.text = text;
} }
public static Value combine(Value left, Value right) {
BaseText text;
if (left instanceof FormattedTextValue)
{
text = (BaseText) ((FormattedTextValue) left).getText().shallowCopy();
}
else
{
if (left instanceof NullValue)
return right;
text = new LiteralText(left.getString());
}
if (right instanceof FormattedTextValue)
{
text.append(((FormattedTextValue) right).getText().shallowCopy());
return new FormattedTextValue(text);
}
else
{
if (right instanceof NullValue)
return left;
text.append(right.getString());
return new FormattedTextValue(text);
}
}
@Override @Override
public String getString() { public String getString() {
return text.getString(); return text.getString();
@@ -48,10 +76,16 @@ public class FormattedTextValue extends StringValue
return StringTag.of(Text.Serializer.toJson(text)); return StringTag.of(Text.Serializer.toJson(text));
} }
@Override
public Value add(Value o) {
return combine(this, o);
}
public String serialize() public String serialize()
{ {
return Text.Serializer.toJson(text); return Text.Serializer.toJson(text);
} }
public static FormattedTextValue deserialize(String serialized) public static FormattedTextValue deserialize(String serialized)
{ {
return new FormattedTextValue(Text.Serializer.fromJson(serialized)); return new FormattedTextValue(Text.Serializer.fromJson(serialized));
@@ -11,7 +11,7 @@ import java.util.Locale;
public abstract class LazyListValue extends AbstractListValue implements Iterator<Value> public abstract class LazyListValue extends AbstractListValue implements Iterator<Value>
{ {
public static LazyListValue range(long from, long to, long step) public static LazyListValue rangeDouble(double from, double to, double step)
{ {
return new LazyListValue() return new LazyListValue()
{ {
@@ -23,10 +23,10 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
this.limit = to; this.limit = to;
this.stepp = step; this.stepp = step;
} }
private long start; private final double start;
private long current; private double current;
private long limit; private final double limit;
private long stepp; private final double stepp;
@Override @Override
public Value next() public Value next()
{ {
@@ -50,7 +50,50 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
@Override @Override
public String getString() public String getString()
{ {
return String.format(Locale.ROOT, "[%d, %d, ..., %d)",start, start+stepp, limit); return String.format(Locale.ROOT, "[%s, %s, ..., %s)",NumericValue.of(start).getString(), NumericValue.of(start+stepp).getString(), NumericValue.of( limit).getString());
}
};
}
public static LazyListValue rangeLong(long from, long to, long step)
{
return new LazyListValue()
{
{
if (step == 0)
throw new InternalExpressionException("Range will never end with a zero step");
this.start = from;
this.current = this.start;
this.limit = to;
this.stepp = step;
}
private final long start;
private long current;
private final long limit;
private final long stepp;
@Override
public Value next()
{
Value val = new NumericValue(current);
current += stepp;
return val;
}
@Override
public void reset()
{
current = start;
}
@Override
public boolean hasNext()
{
return stepp > 0?(current < limit):(current > limit);
}
@Override
public String getString()
{
return String.format(Locale.ROOT, "[%s, %s, ..., %s)",NumericValue.of(start).getString(), NumericValue.of(start+stepp).getString(), NumericValue.of( limit).getString());
} }
}; };
} }
@@ -267,7 +267,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa
} }
} }
private static Map<String,ItemStackArgument> itemCache = new HashMap<>(); private static final Map<String,ItemStackArgument> itemCache = new HashMap<>();
public static ItemStackArgument parseItem(String itemString) public static ItemStackArgument parseItem(String itemString)
{ {
@@ -15,6 +15,7 @@ import java.math.RoundingMode;
import java.util.Locale; import java.util.Locale;
import static java.lang.Math.abs; import static java.lang.Math.abs;
import static java.lang.Math.signum;
public class NumericValue extends Value public class NumericValue extends Value
{ {
@@ -57,6 +58,7 @@ public class NumericValue extends Value
{ {
if (value.isInfinite()) return "INFINITY"; if (value.isInfinite()) return "INFINITY";
if (value.isNaN()) return "NaN"; if (value.isNaN()) return "NaN";
if (abs(value) < epsilon) return (signum(value) < 0)?"-0":"0"; //zero rounding fails with big decimals
// dobules have 16 point precision, 12 is plenty to display // dobules have 16 point precision, 12 is plenty to display
return BigDecimal.valueOf(value).round(displayRounding).stripTrailingZeros().toPlainString(); return BigDecimal.valueOf(value).round(displayRounding).stripTrailingZeros().toPlainString();
} }
@@ -306,4 +308,9 @@ public class NumericValue extends Value
if (longValue != null) return new NumericValue(-longValue); if (longValue != null) return new NumericValue(-longValue);
return new NumericValue(-value); return new NumericValue(-value);
} }
public boolean isInteger()
{
return longValue!= null || getDouble() == (double)getLong();
}
} }
+10 -4
View File
@@ -13,10 +13,12 @@ import java.util.regex.PatternSyntaxException;
public abstract class Value implements Comparable<Value>, Cloneable public abstract class Value implements Comparable<Value>, Cloneable
{ {
public static Value FALSE = new NumericValue(0); public static NumericValue FALSE = new NumericValue(0);
public static Value TRUE = new NumericValue(1); public static NumericValue TRUE = new NumericValue(1);
public static Value ZERO = FALSE; public static NumericValue ZERO = FALSE;
public static Value NULL = new NullValue(); public static NumericValue ONE = TRUE;
public static NullValue NULL = new NullValue();
public String boundVariable; public String boundVariable;
@@ -77,6 +79,10 @@ public abstract class Value implements Comparable<Value>, Cloneable
public Value add(Value o) { public Value add(Value o) {
String lstr = this.getString(); String lstr = this.getString();
if (o instanceof FormattedTextValue)
{
return FormattedTextValue.combine(this, o);
}
if (lstr == null) // null if (lstr == null) // null
return new StringValue(o.getString()); return new StringValue(o.getString());
String rstr = o.getString(); String rstr = o.getString();
@@ -145,9 +145,8 @@ global_functions = {
[10+half_width,10+height,10+half_width], [10+half_width,10+height,10+half_width],
0x65432100, 'buddy detection', false 0x65432100, 'buddy detection', false
); );
[x,y,z] = pos(e);
current_id = e~'id'; current_id = e~'id';
buddies = entity_area('villager', x, y+height/2, z, 10+half_width, 10+height/2, 10+half_width); buddies = entity_area('villager', e, 10, 10, 10);
nb = length(buddies); nb = length(buddies);
for (filter(buddies, _~'id' != current_id), for (filter(buddies, _~'id' != current_id),
visuals+=['line', global_duration, 'from', pos(e), 'to', pos(_), 'color', 0xffff00ff]; visuals+=['line', global_duration, 'from', pos(e), 'to', pos(_), 'color', 0xffff00ff];
@@ -518,9 +517,8 @@ __tick_tracker() ->
return() return()
); );
p = player(); p = player();
[px, py, pz] = pos(p);
in_dimension(p, in_dimension(p,
for (entity_area('valid', px, py, pz, global_range, global_range, global_range), for (entity_area('valid', p, global_range, global_range, global_range),
__handle_entity(_) __handle_entity(_)
) )
); );
@@ -1,91 +1,155 @@
import('math','_euclidean','_manhattan','_round'); import('math','_euclidean','_manhattan','_round');
global_display_modes = ['chat', 'line', 'centered_sphere', 'box'];
__config()->{ __config()->{
'commands'->{ 'commands'->{
'from <start>'->'set_start', 'from <start>'->'set_start',
'from <start> to <end>'->'_calculate', 'from <start> to <end>'-> _(start, end) -> (global_current_start = start; calculate(end)),
'to <end>'->_(end_pos)->(_calculate(null,end_pos)), 'to <end>'-> 'calculate',
'toggle_line'->'_toggle_line' 'clear' -> _() -> global_display_shapes = [],
'clear last' -> _() -> if(global_display_shapes, delete(global_display_shapes, -1)),
'display <mode>' -> 'set_mode',
'assist <assist>' -> 'set_assist',
'assist none' -> _() -> set_assist(null),
}, },
'arguments' -> { 'arguments' -> {
'start' -> {'type' -> 'location'}, 'start' -> {'type' -> 'location'},
'end' -> {'type' -> 'location'} 'end' -> {'type' -> 'location'},
'mode' -> {'type' -> 'term', 'options' -> global_display_modes },
'assist' -> {'type' -> 'block', 'suggest' -> ['brown_carpet']},
} }
}; };
global_start=null; global_current_start = null;
global_end=null; global_display_shapes = [];
global_renderer_running = false;
global_current_mode = 'chat';
global_show_line=false; global_assist_block = null;
set_assist(block) ->
(
if (block == null,
global_assist_block = null;
handle_event('player_places_block', null);
handle_event('player_uses_item', null);
,
global_assist_block = str(block);
handle_event('player_places_block', 'on_player_places_block');
handle_event('player_uses_item', 'on_player_uses_item');
)
);
global_generators = {
'line' -> _(from, to) -> [
['line', 'from',from,'to',to]
],
'centered_sphere' -> _(from, to) -> [
['line', 'from',from,'to',to, 'line', 5, 'color', 0x88888888],
['sphere', 'center',from,'radius',_euclidean(from,to)]
],
'box' -> _(from, to) -> [
['line', 'from',from,'to',to, 'line', 5, 'color', 0x88888888],
['box', 'from',from,'to',to]
],
};
_create_shapes(mode, from, to) ->
(
shapes = call(global_generators:mode, from+[0, 0.07, 0], to+[0, 0.07, 0]);
shapes += ['label', 'pos',to+[0,0.2,0], 'align', 'right', 'indent', -1.5, 'text',format('rb Cylindrical:')];
shapes += ['label', 'pos',to+[0,0.2,0], 'align', 'left', 'text',_round(_euclidean(from, [to:0, from:1, to:2]), 0.001)];
shapes += ['label', 'pos',to+[0,0.2,0], 'height', 1, 'align', 'right', 'indent', -1.5, 'text',format('rb Manhattan:')];
shapes += ['label', 'pos',to+[0,0.2,0], 'height', 1, 'align', 'left', 'text',_round(_manhattan(from,to),0.001)];
shapes += ['label', 'pos',to+[0,0.2,0], 'height', 2, 'align', 'right', 'indent', -1.5, 'text',format('rb Euclidian:')];
shapes += ['label', 'pos',to+[0,0.2,0], 'height', 2, 'align', 'left', 'text',_round(_euclidean(from,to),0.001)];
map(shapes, put(_, 1, [20, 'player', player()], 'extend'); _);
);
__mktp(pos) -> str('!/tp %.6f %.6f %.6f', pos); __mktp(pos) -> str('!/tp %.6f %.6f %.6f', pos);
set_start(pos)->( set_start(pos)->(
global_start = pos; global_current_start = pos;
global_end = null;
print(player(),format('gi Initial point set to: ', 'g '+pos, __mktp(pos))) print(player(),format('gi Initial point set to: ', 'g '+pos, __mktp(pos)))
); );
_toggle_line()->( set_mode(mode)->(
global_show_line= !global_show_line; if (global_display_modes ~ mode,
if(global_show_line, global_current_mode = mode
print('Toggled lines on');
_draw_line()
, // else
print('Toggled lines off')
);
);
_calculate(start_pos, end_pos)->(
if(start_pos!=null,
set_start(start_pos);
calculate(end_pos)
, // else
if(global_start,
calculate(end_pos),
print(player(),format('r There was no initial point for '+player()));
set_start(end_pos)
)
) )
); );
__on_player_places_block(player, item_tuple, hand, block) ->( calculate(end)->(
if(block=='brown_carpet' && system_info('world_carpet_rules'):'carpets'=='true', if(global_current_start != null,
measure(end)
, // else
print(player(),format('r No initial point selected'));
)
);
on_player_places_block(player, item_tuple, hand, block) ->
(
if(block==global_assist_block,
effective_pos = pos(block)+[0.5, 0, 0.5]; effective_pos = pos(block)+[0.5, 0, 0.5];
if(global_start==null||player~'sneaking',//wont complain for first carpet if(global_current_start==null||player~'sneaking',//wont complain for first carpet
set_start(effective_pos), set_start(effective_pos)
_calculate(null, effective_pos) , // else
calculate(effective_pos)
) )
) )
); );
calculate(end_pos)->( on_player_uses_item(player, item_tuple, hand) ->
global_end = end_pos; (
manhattan = _round(_manhattan(global_start, global_end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places if (item_tuple:0 == global_assist_block && hand == 'mainhand',
spherical = _round(_euclidean(global_start, global_end), 0.001); if (player ~'sneaking',
//useful for chunk distances etc. global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1);
cylindrical = _round(_euclidean(global_start, [global_end:0, global_start:1, global_end:2]), 0.001); display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode));
,
if(global_display_shapes, delete(global_display_shapes, -1))
print(player(),format( )
'w Distance between ',
'c '+str('[%.1f, %.1f, %.1f]', global_start), __mktp(global_start),
'w and ',
'c '+str('[%.1f, %.1f, %.1f]', global_end), __mktp(global_end), 'w :\n',
'w - Spherical: ', 'wb '+spherical+'\n',
'w - Cylindrical: ', 'wb '+cylindrical+'\n',
'w - Manhattan: ', 'wb '+manhattan
));
schedule(0,'_draw_line');
);
_draw_line()->(
if(global_show_line,//It will always draw from the global positions, so it will turn on if there is a line
if ( global_start && global_end,
draw_shape('line',20,'from',global_start,'to',global_end, 'player',player());
draw_shape('label',20,'pos',global_end,'text',_round(_euclidean(global_start,global_end),0.001));
);
schedule(20,'_draw_line')
) )
); );
measure(end)->
(
if (global_current_start == null, return());
start = global_current_start;
if (global_current_mode == 'chat',
manhattan = _round(_manhattan(start, end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places
spherical = _round(_euclidean(start, end), 0.001);
//useful for chunk distances etc.
cylindrical = _round(_euclidean(start, [end:0, start:1, end:2]), 0.001);
print(player(),format(
'w Distance between ',
'c '+str('[%.1f, %.1f, %.1f]', start), __mktp(start),
'w and ',
'c '+str('[%.1f, %.1f, %.1f]', end), __mktp(end), 'w :\n',
'w - Spherical: ', 'wb '+spherical+'\n',
'w - Cylindrical: ', 'wb '+cylindrical+'\n',
'w - Manhattan: ', 'wb '+manhattan
));
, // else
global_display_shapes += _create_shapes(global_current_mode, start, end);
for (global_display_shapes, draw_shape(_) );
if (!global_renderer_running,
global_renderer_running = true;
render()
)
)
);
render()->(
if (!global_display_shapes,
global_renderer_running = false;
return();
);
for (global_display_shapes,
draw_shape(_);
);
schedule(20,'render')
);
@@ -20,7 +20,7 @@ __config() -> {
'arguments'->{ 'arguments'->{
'center'->{'type'->'pos', 'loaded'->'true'}, 'center'->{'type'->'pos', 'loaded'->'true'},
'radius'->{'type'->'int', 'suggest'->[], 'min'->0},//to avoid default suggestions 'radius'->{'type'->'int', 'suggest'->[], 'min'->0},//to avoid default suggestions
'replacement'->{'type'->'block'}, 'replacement'->{'type'->'blockpredicate'},
'height'->{'type'->'int', 'suggest'->[],'min'->0}, 'height'->{'type'->'int', 'suggest'->[],'min'->0},
'orientation'->{'type'->'term', 'suggest'->['x','y','z']}, 'orientation'->{'type'->'term', 'suggest'->['x','y','z']},
'pointing'->{'type'->'term','suggest'->['up','down']}, 'pointing'->{'type'->'term','suggest'->['up','down']},
@@ -32,9 +32,20 @@ __config() -> {
//"Boilerplate" code //"Boilerplate" code
_block_matches(existing, block_predicate) ->
(
[name, block_tag, properties, nbt] = block_predicate;
(name == null || name == existing) &&
(block_tag == null || block_tags(existing, block_tag)) &&
all(properties, block_state(existing, _) == properties:_) &&
(!tag || tag_matches(block_data(existing), tag))
);
set_block(x, y, z, block, replacement)-> ( set_block(x, y, z, block, replacement)-> (
if(block != block(x, y, z), existing = block(x, y, z);
without_updates( if (set([x, y, z],block), if(block != existing && (replacement && _block_matches(existing, replacement) ),
without_updates( if (set(existing,block),
global_affected += [x, y, z]; global_affected += [x, y, z];
)); ));
); );
@@ -292,3 +292,14 @@ __on_player_picks_up_item(player, item) ->
print(' Player '+player+' injects '+item); print(' Player '+player+' injects '+item);
); );
__on_player_trades(player, entity, buy_left, buy_right, sell) ->
(
print('');
print('__on_player_trades(player, item)');
print('player trades:');
print(' - player: '+player);
print(' - entity: '+ entity);
print(' - buy_left: '+ buy_left);
print(' - buy_right: '+ buy_right);
print(' - sell: '+ sell)
);
@@ -36,22 +36,15 @@ __config() ->
'slime_chunks' -> ['__toggle', 'slime_chunks', 'chunks'], 'slime_chunks' -> ['__toggle', 'slime_chunks', 'chunks'],
'portal coordinates' -> ['__toggle', 'coords', 'portals'], 'portal coordinates' -> ['__toggle', 'coords', 'portals'],
'portal links' -> ['__toggle', 'links', 'portals'], 'portal links' -> ['__toggle', 'links', 'portals'],
'<shape> <radius> following <player>' -> ['display_shape', [null,0xffffffff], true], '<shape> <radius> following <entities>' -> ['display_shape', [null,0xffffffff], true],
'<shape> <radius> at <player>' -> ['display_shape', [null,0xffffffff], false], '<shape> <radius> at <entities>' -> ['display_shape', [null,0xffffffff], false],
'<shape> <radius> following <player> <color>' -> ['display_shape', true], '<shape> <radius> following <entities> <color>' -> ['display_shape', true],
'<shape> <radius> at <player> <color>' -> ['display_shape', false], '<shape> <radius> at <entities> <color>' -> ['display_shape', false],
'<shape> clear' -> 'clear_shape', '<shape> clear' -> 'clear_shape',
'clear' -> 'clear', 'clear' -> 'clear',
}, },
'arguments' -> { 'arguments' -> {
'structure' -> {'type' -> 'term', 'suggest' -> [ 'structure' -> {'type' -> 'term', 'suggest' -> plop():'structures' },
'monument', 'fortress', 'mansion', 'jungle_pyramid',
'desert_pyramid', 'endcity', 'igloo', 'shipwreck',
'swamp_hut', 'stronghold', 'ocean_ruin', 'buried_treasure',
'pillager_outpost', 'mineshaft', 'village', 'nether_fossil',
'bastion_remnant', 'ruined_portal'
]},
'player' -> {'type' -> 'players', 'single' -> true},
'radius' -> {'type' -> 'int', 'min' -> 0, 'max' -> 1024, 'suggest' -> [128, 24, 32]}, 'radius' -> {'type' -> 'int', 'min' -> 0, 'max' -> 1024, 'suggest' -> [128, 24, 32]},
'shape' -> {'type' -> 'term', 'options' -> keys(global_shapes) }, 'shape' -> {'type' -> 'term', 'options' -> keys(global_shapes) },
'color' -> {'type' -> 'teamcolor'} 'color' -> {'type' -> 'teamcolor'}
@@ -59,29 +52,30 @@ __config() ->
}; };
display_shape(shape, r, player, color, following) -> display_shape(shape, r, entities, color, following) ->
( (
thicc = max(1, floor(6-length(entities)/2));
player = player(player); fillc = 250 - 100/(length(entities)+1);
if (!player, return()); for (entities,
if (shape == 'sphere', if (shape == 'sphere',
shape_config = { shape_config = {
'center' -> if(following, [0,0,0], pos(player)), 'center' -> if(following, [0,0,0], pos(_)),
'radius' -> r 'radius' -> r
} }
, shape == 'box', , shape == 'box',
shape_config = { shape_config = {
'from' -> if(following, [-r,-r,-r], pos(player)-r), 'from' -> if(following, [-r,-r,-r], pos(_)-r),
'to' -> if(following, [r,r,r], pos(player)+r) 'to' -> if(following, [r,r,r], pos(_)+r)
} }
); );
if (shape_config, if (shape_config,
if (following, shape_config:'follow' = player); if (following, shape_config:'follow' = _);
shape_config:'fill' = color:1 - 200; shape_config:'fill' = color:1 - fillc;
shape_config:'color' = color:1; shape_config:'color' = color:1;
shape_config:'line' = 5; shape_config:'line' = thicc;
draw_shape(shape, 72000, shape_config); draw_shape(shape, 72000, shape_config);
global_shapes:shape += shape_config; global_shapes:shape += shape_config;
)
) )
); );
+2 -1
View File
@@ -166,11 +166,12 @@
"PlayerInventory_scarpetEventMixin", "PlayerInventory_scarpetEventMixin",
"ScoreboardObjective_scarpetMixin", "ScoreboardObjective_scarpetMixin",
"Scoreboard_scarpetMixin", "Scoreboard_scarpetMixin",
"TradeOutputSlot_scarpetEventMixin",
"ChunkTicketManager_scarpetMixin", "ChunkTicketManager_scarpetMixin",
"StatType_scarpetMixin", "StatType_scarpetMixin",
"PointOfInterest_scarpetMixin", "PointOfInterest_scarpetMixin",
"ServerWorld_scarpetEventMixin", "ServerWorld_scarpetMixin",
"HungerManagerMixin_scarpetEventsMixin", "HungerManagerMixin_scarpetEventsMixin",
"RecipeManager_scarpetMixin", "RecipeManager_scarpetMixin",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"id": "carpet", "id": "carpet",
"version": "1.4.19", "version": "1.4.21",
"name": "Carpet Mod in Fabric", "name": "Carpet Mod in Fabric",
"description": "Carpet made out of fabric", "description": "Carpet made out of fabric",