Merge branch 'master' into scoreboard-criterion-mod
This commit is contained in:
63 files changed
+2386
-797
No files matched your search
+6
-1
@@ -6,6 +6,10 @@ out/
|
||||
classes/
|
||||
publish/
|
||||
|
||||
# git
|
||||
|
||||
.shelf/
|
||||
|
||||
# idea
|
||||
|
||||
.idea/
|
||||
@@ -27,4 +31,5 @@ bin/
|
||||
|
||||
# fabric
|
||||
|
||||
run/
|
||||
run/
|
||||
logs/
|
||||
+347
-165
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
|
||||
## Code delivery, line indicators
|
||||
|
||||
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
|
||||
loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only
|
||||
contain code, no need to start with "/script run" prefix
|
||||
your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files
|
||||
or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
</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)`
|
||||
|
||||
|
||||
@@ -1372,7 +1376,7 @@ foo(... x) -> ... # all arguments for foo are included in the list
|
||||
### `import(module_name, symbols ...)`
|
||||
|
||||
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.
|
||||
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.
|
||||
@@ -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
|
||||
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.
|
||||
|
||||
#### 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
|
||||
creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
|
||||
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
|
||||
range(5,10) => [5, 6, 7, 8, 9]
|
||||
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>
|
||||
|
||||
## Map operations
|
||||
@@ -1705,7 +1759,9 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript
|
||||
## 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`
|
||||
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
|
||||
`__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).
|
||||
@@ -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.
|
||||
Even for `'player'` scoped apps, you can access specific player app with with commandblocks using
|
||||
`/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
|
||||
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.
|
||||
* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will
|
||||
running anything in the global scope for a `'player'` scoped app is not intended.
|
||||
* `'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.
|
||||
This is to allow to store multiple apps on the server/world and selectively decide which one should be running at
|
||||
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.
|
||||
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.
|
||||
* `'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
|
||||
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
|
||||
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.
|
||||
* `'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
|
||||
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
|
||||
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.
|
||||
|
||||
## 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
|
||||
to be loaded.
|
||||
* `'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.
|
||||
* `'columnpos'`: a pair of x and z coordinates.
|
||||
* `'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
|
||||
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.
|
||||
Second part, `block` is either block value as a result of `block()` function string value indicating the block name,
|
||||
and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to
|
||||
First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value.
|
||||
Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name
|
||||
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.
|
||||
|
||||
If `block` is specified only by name, then if a
|
||||
destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
|
||||
properties.
|
||||
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>
|
||||
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))
|
||||
// yet another option, skips all parsing
|
||||
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, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
|
||||
|
||||
set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor
|
||||
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', 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>
|
||||
|
||||
### `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
|
||||
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
|
||||
|
||||
### `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`.
|
||||
|
||||
### `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
|
||||
by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in
|
||||
positive values.
|
||||
`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
|
||||
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 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.
|
||||
|
||||
`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,
|
||||
@@ -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
|
||||
</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
|
||||
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.
|
||||
|
||||
`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.`
|
||||
|
||||
### `diamond(centre_pos, radius?, height?)`
|
||||
@@ -2840,7 +2937,9 @@ to the `regular` group.
|
||||
* `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
|
||||
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`,
|
||||
`chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`,
|
||||
`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
|
||||
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
|
||||
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
|
||||
cuboid search area.
|
||||
@@ -2994,13 +3096,21 @@ List of entities riding the entity.
|
||||
|
||||
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')`
|
||||
|
||||
@@ -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.
|
||||
|
||||
### `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?)`
|
||||
|
||||
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`.
|
||||
|
||||
### `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')`
|
||||
|
||||
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
|
||||
|
||||
### `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)`
|
||||
|
||||
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
|
||||
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?)`
|
||||
|
||||
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.
|
||||
|
||||
### 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)`
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
in `'player'` scoped app.
|
||||
|
||||
`'global'` scoped apps will receive both global and player built-in events.
|
||||
all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
|
||||
player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
|
||||
|
||||
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
|
||||
@@ -3822,8 +3973,15 @@ if it accepts required number of parameters.
|
||||
|
||||
## Built-in global events
|
||||
|
||||
Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you
|
||||
simply wouldn't know which player to hook up this event to.
|
||||
Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
|
||||
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()`
|
||||
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)`
|
||||
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
|
||||
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)`
|
||||
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
|
||||
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
|
||||
is specified.
|
||||
either targetting all apps instances for all players, including global apps, if no target player has been identified,
|
||||
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
|
||||
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
|
||||
```
|
||||
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 ...)`
|
||||
@@ -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,
|
||||
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>
|
||||
foo(a) -> print(a);
|
||||
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
|
||||
</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?)`
|
||||
|
||||
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`,
|
||||
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.
|
||||
If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped.
|
||||
Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover
|
||||
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
|
||||
If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
|
||||
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.
|
||||
|
||||
<pre>
|
||||
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('tick') // triggering tick event in all loaded apps
|
||||
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
|
||||
signal_event('tick') // trigger all apps with a tick event
|
||||
</pre>
|
||||
|
||||
## Custom events example
|
||||
|
||||
The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous
|
||||
squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but
|
||||
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
|
||||
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.
|
||||
The following example shows how you can communicate between different instances of the same player scoped app. It important to note
|
||||
that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called
|
||||
`tp_request` and is triggered with a command.
|
||||
|
||||
|
||||
```
|
||||
// detector1.sc
|
||||
// detecting event in a tick loop
|
||||
__config() -> {'scope' -> 'global'};
|
||||
|
||||
global_sneaks = {};
|
||||
__on_tick() -> for(player('all'),
|
||||
if (_~'sneaking',
|
||||
sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0);
|
||||
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
|
||||
// tpa.sc
|
||||
global_requester = null;
|
||||
__config() -> {
|
||||
'commands' -> {
|
||||
'<player>' -> _(to) -> signal_event('tp_request', to, player()),
|
||||
'accept' -> _() -> if(global_requester,
|
||||
run('tp '+global_requester~'command_name');
|
||||
global_requester = null
|
||||
)
|
||||
, // 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
|
||||
|
||||
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
|
||||
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(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
|
||||
```
|
||||
|
||||
## `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
|
||||
|
||||
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
|
||||
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.
|
||||
Can optionally define player or list of players to send the message to.
|
||||
|
||||
### `format(components, ...)`, `format(l(components, ...))`
|
||||
|
||||
@@ -4544,13 +4720,15 @@ Available output types:
|
||||
### `read_file(resource, type)`
|
||||
### `delete_file(resource, type)`
|
||||
### `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
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true`
|
||||
for success writes and deletes, and requested data, based on the file type, for read operations.
|
||||
|
||||
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'))
|
||||
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. It returns list of files
|
||||
for folder listing.
|
||||
|
||||
To log a single line of string
|
||||
|
||||
Supported values for resource `type` is:
|
||||
Supported values for resource `type` are:
|
||||
* `nbt` - NBT tag
|
||||
* `text` - text resource with automatic newlines added
|
||||
* `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,
|
||||
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
|
||||
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>
|
||||
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_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
|
||||
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)`
|
||||
|
||||
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_folder` - name of the direct folder in the saves that holds world files
|
||||
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
|
||||
|
||||
* `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
|
||||
|
||||
|
||||
Relevant gameplay related properties
|
||||
* `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
|
||||
* `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
|
||||
|
||||
Server related properties
|
||||
|
||||
* `server_motd` - the motd of the server visible when joining
|
||||
* `server_ip` - IP adress of the game hosted
|
||||
* `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_banned_players` - list of banned player names
|
||||
* `server_banned_ips` - list of banned IP addresses
|
||||
* `server_dev_environment` - boolean indicating whether this server is in a development environment.
|
||||
|
||||
System related properties
|
||||
* `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_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_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,
|
||||
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
|
||||
|
||||
`load / unload` commands allow for very conventient 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
|
||||
/scripts folder of your world files and make sure it ends with `.sc` extension. The good thing about editing that
|
||||
code is that you can no only use normal editing without the need of marking of newlines,
|
||||
`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
|
||||
`/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can
|
||||
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.
|
||||
|
||||
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>
|
||||
foo = 1;
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
Can optionally define player or list of players to send the message to.
|
||||
|
||||
### `format(components, ...)`, `format(l(components, ...))`
|
||||
|
||||
@@ -275,13 +276,15 @@ Available output types:
|
||||
### `read_file(resource, type)`
|
||||
### `delete_file(resource, type)`
|
||||
### `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
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true`
|
||||
for success writes and deletes, and requested data, based on the file type, for read operations.
|
||||
|
||||
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'))
|
||||
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. It returns list of files
|
||||
for folder listing.
|
||||
|
||||
To log a single line of string
|
||||
|
||||
Supported values for resource `type` is:
|
||||
Supported values for resource `type` are:
|
||||
* `nbt` - NBT tag
|
||||
* `text` - text resource with automatic newlines added
|
||||
* `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,
|
||||
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
|
||||
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>
|
||||
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_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
|
||||
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)`
|
||||
|
||||
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_folder` - name of the direct folder in the saves that holds world files
|
||||
* `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event.
|
||||
|
||||
* `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`.
|
||||
|
||||
|
||||
Relevant gameplay related properties
|
||||
* `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'`
|
||||
* `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
|
||||
|
||||
Server related properties
|
||||
|
||||
* `server_motd` - the motd of the server visible when joining
|
||||
* `server_ip` - IP adress of the game hosted
|
||||
* `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_banned_players` - list of banned player names
|
||||
* `server_banned_ips` - list of banned IP addresses
|
||||
* `server_dev_environment` - boolean indicating whether this server is in a development environment.
|
||||
|
||||
System related properties
|
||||
* `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_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_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.
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
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
|
||||
by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in
|
||||
positive values.
|
||||
`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
|
||||
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 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.
|
||||
|
||||
`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,
|
||||
@@ -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
|
||||
</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
|
||||
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.
|
||||
|
||||
`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.`
|
||||
|
||||
### `diamond(centre_pos, radius?, height?)`
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
Second part, `block` is either block value as a result of `block()` function string value indicating the block name,
|
||||
and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to
|
||||
First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value.
|
||||
Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name
|
||||
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.
|
||||
|
||||
If `block` is specified only by name, then if a
|
||||
destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra
|
||||
properties.
|
||||
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>
|
||||
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))
|
||||
// yet another option, skips all parsing
|
||||
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, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
|
||||
|
||||
set(x,y,z,'iron_trapdoor[half=top]') // sets the top trapdoor
|
||||
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', 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>
|
||||
|
||||
### `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
|
||||
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
|
||||
|
||||
### `pos(block), pos(entity)`
|
||||
|
||||
@@ -52,7 +52,9 @@ to the `regular` group.
|
||||
* `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
|
||||
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`,
|
||||
`chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`,
|
||||
`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
|
||||
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
|
||||
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
|
||||
cuboid search area.
|
||||
@@ -206,13 +211,21 @@ List of entities riding the entity.
|
||||
|
||||
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')`
|
||||
|
||||
@@ -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.
|
||||
|
||||
### `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?)`
|
||||
|
||||
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`.
|
||||
|
||||
### `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')`
|
||||
|
||||
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
|
||||
|
||||
### `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)`
|
||||
|
||||
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
|
||||
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?)`
|
||||
|
||||
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
|
||||
|
||||
+63
-100
@@ -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.
|
||||
|
||||
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
|
||||
in `'player'` scoped app.
|
||||
|
||||
`'global'` scoped apps will receive both global and player built-in events.
|
||||
all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
|
||||
player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
|
||||
|
||||
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
|
||||
@@ -31,8 +29,15 @@ if it accepts required number of parameters.
|
||||
|
||||
## Built-in global events
|
||||
|
||||
Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you
|
||||
simply wouldn't know which player to hook up this event to.
|
||||
Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
|
||||
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()`
|
||||
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)`
|
||||
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
|
||||
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)`
|
||||
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
|
||||
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
|
||||
is specified.
|
||||
either targetting all apps instances for all players, including global apps, if no target player has been identified,
|
||||
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
|
||||
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
|
||||
```
|
||||
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 ...)`
|
||||
@@ -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,
|
||||
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>
|
||||
foo(a) -> print(a);
|
||||
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
|
||||
</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?)`
|
||||
|
||||
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`,
|
||||
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.
|
||||
If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped.
|
||||
Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover
|
||||
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
|
||||
If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
|
||||
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.
|
||||
|
||||
<pre>
|
||||
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('tick') // triggering tick event in all loaded apps
|
||||
signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances
|
||||
signal_event('tick') // trigger all apps with a tick event
|
||||
</pre>
|
||||
|
||||
## Custom events example
|
||||
|
||||
The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous
|
||||
squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but
|
||||
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
|
||||
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.
|
||||
The following example shows how you can communicate between different instances of the same player scoped app. It important to note
|
||||
that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called
|
||||
`tp_request` and is triggered with a command.
|
||||
|
||||
|
||||
```
|
||||
// detector1.sc
|
||||
// detecting event in a tick loop
|
||||
__config() -> {'scope' -> 'global'};
|
||||
|
||||
global_sneaks = {};
|
||||
__on_tick() -> for(player('all'),
|
||||
if (_~'sneaking',
|
||||
sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0);
|
||||
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
|
||||
// tpa.sc
|
||||
global_requester = null;
|
||||
__config() -> {
|
||||
'commands' -> {
|
||||
'<player>' -> _(to) -> signal_event('tp_request', to, player()),
|
||||
'accept' -> _() -> if(global_requester,
|
||||
run('tp '+global_requester~'command_name');
|
||||
global_requester = null
|
||||
)
|
||||
, // 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
|
||||
|
||||
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
|
||||
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.
|
||||
@@ -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.
|
||||
|
||||
### 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)`
|
||||
|
||||
Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets),
|
||||
|
||||
@@ -6,7 +6,9 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript
|
||||
## 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`
|
||||
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
|
||||
`__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).
|
||||
@@ -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.
|
||||
Even for `'player'` scoped apps, you can access specific player app with with commandblocks using
|
||||
`/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
|
||||
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.
|
||||
* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will
|
||||
running anything in the global scope for a `'player'` scoped app is not intended.
|
||||
* `'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.
|
||||
This is to allow to store multiple apps on the server/world and selectively decide which one should be running at
|
||||
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.
|
||||
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.
|
||||
* `'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
|
||||
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
|
||||
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.
|
||||
* `'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
|
||||
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
|
||||
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.
|
||||
|
||||
## 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
|
||||
to be loaded.
|
||||
* `'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.
|
||||
* `'columnpos'`: a pair of x and z coordinates.
|
||||
* `'dimension'`: string representing a valid dimension in the world.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
## `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
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,13 +8,15 @@ to simulate running commands in a different scope.
|
||||
|
||||
# `/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
|
||||
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
|
||||
code is that you can no only use normal editing without the need of marking of newlines,
|
||||
`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
|
||||
`/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can
|
||||
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.
|
||||
|
||||
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>
|
||||
foo = 1;
|
||||
|
||||
@@ -215,6 +215,8 @@ map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
||||
reduce(range(10),_a+_, 0) => 45
|
||||
range(5,10) => [5, 6, 7, 8, 9]
|
||||
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>
|
||||
|
||||
## Map operations
|
||||
|
||||
@@ -135,7 +135,7 @@ foo(... x) -> ... # all arguments for foo are included in the list
|
||||
### `import(module_name, symbols ...)`
|
||||
|
||||
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.
|
||||
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.
|
||||
@@ -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
|
||||
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.
|
||||
|
||||
#### 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
|
||||
creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for
|
||||
programmers with their own lambda arguments
|
||||
|
||||
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
|
||||
## Code delivery, line indicators
|
||||
|
||||
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
|
||||
loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only
|
||||
contain code, no need to start with "/script run" prefix
|
||||
your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files
|
||||
or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded
|
||||
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
|
||||
|
||||
|
||||
@@ -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.
|
||||
</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)`
|
||||
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ block_tick
|
||||
block_tags
|
||||
blocks_daylight
|
||||
blocks_movement
|
||||
bossbar
|
||||
brightness
|
||||
chunk_tickets
|
||||
convert_date
|
||||
crafting_remaining_item
|
||||
create_marker
|
||||
current_dimension
|
||||
@@ -51,9 +53,12 @@ inventory_remove
|
||||
inventory_set
|
||||
inventory_size
|
||||
item_category
|
||||
item_list
|
||||
item_tags
|
||||
last_tick_times
|
||||
light
|
||||
liquid
|
||||
list_files
|
||||
load_app_data
|
||||
loaded
|
||||
loaded_status
|
||||
@@ -92,10 +97,10 @@ scoreboard_remove
|
||||
scoreboard_display
|
||||
tag_matches
|
||||
team_add
|
||||
team_list
|
||||
team_remove
|
||||
team_leave
|
||||
team_list
|
||||
team_property
|
||||
team_remove
|
||||
read_file
|
||||
see_sky
|
||||
set
|
||||
@@ -121,8 +126,9 @@ ticks_randomly
|
||||
top
|
||||
transparent
|
||||
update
|
||||
unix_time
|
||||
view_distance
|
||||
volume
|
||||
without_updates
|
||||
world_time
|
||||
write_file
|
||||
write_file
|
||||
@@ -58,15 +58,19 @@
|
||||
structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension
|
||||
add_chunk_ticket
|
||||
|
||||
convert_date unix_time
|
||||
|
||||
display_title
|
||||
|
||||
scoreboard scoreboard_add scoreboard_remove scoreboard_display
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -80,7 +84,7 @@
|
||||
|
||||
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
|
||||
</Keywords>
|
||||
<Keywords name="Keywords3">euler pi null true false</Keywords>
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G
|
||||
loader_version=0.10.8
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 1.4.19+v201125
|
||||
mod_version = 1.4.21+v201216
|
||||
maven_group = carpet
|
||||
archives_base_name = fabric-carpet
|
||||
|
||||
|
||||
@@ -5,7 +5,19 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.helpers.HopperCounter;
|
||||
import carpet.helpers.TickSpeed;
|
||||
@@ -90,9 +102,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
|
||||
|
||||
public static void onServerLoadedWorlds(MinecraftServer minecraftServer)
|
||||
{
|
||||
HopperCounter.resetAll(minecraftServer);
|
||||
extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer));
|
||||
scriptServer.initializeForWorld();
|
||||
HopperCounter.resetAll(minecraftServer);
|
||||
}
|
||||
|
||||
public static void tick(MinecraftServer server)
|
||||
@@ -143,6 +155,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
|
||||
{
|
||||
ServerNetworkHandler.onPlayerJoin(player);
|
||||
LoggerRegistry.playerConnected(player);
|
||||
scriptServer.onPlayerJoin(player);
|
||||
extensions.forEach(e -> e.onPlayerLoggedIn(player));
|
||||
|
||||
}
|
||||
@@ -156,9 +169,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit
|
||||
|
||||
public static void onServerClosed(MinecraftServer server)
|
||||
{
|
||||
if (scriptServer != null) scriptServer.onClose();
|
||||
ServerNetworkHandler.close();
|
||||
currentCommandDispatcher = null;
|
||||
if (scriptServer != null) scriptServer.onClose();
|
||||
|
||||
LoggerRegistry.stopLoggers();
|
||||
extensions.forEach(e -> e.onServerClosed(server));
|
||||
|
||||
@@ -36,7 +36,7 @@ import static carpet.settings.RuleCategory.CLIENT;
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
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 boolean skipGenerationChecks = false;
|
||||
public static boolean impendingFillSkipUpdates = false;
|
||||
@@ -427,7 +427,7 @@ public class CarpetSettings
|
||||
extra = "if /script is enabled",
|
||||
category = SCARPET
|
||||
)
|
||||
public static boolean scriptsAutoload = false;
|
||||
public static boolean scriptsAutoload = true;
|
||||
|
||||
@Rule(desc = "Enables /player command to control/spawn players", category = COMMAND)
|
||||
public static String commandPlayer = "ops";
|
||||
|
||||
@@ -58,6 +58,9 @@ public class PlayerCommand
|
||||
.then(makeActionCommand("dropStack", EntityPlayerActionPack.ActionType.DROP_STACK))
|
||||
.then(makeDropCommand("dropStack", true))
|
||||
.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("shadow"). executes(PlayerCommand::shadow))
|
||||
.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.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.minecraft.block.Block;
|
||||
@@ -84,7 +85,7 @@ public class ScriptCommand
|
||||
private static CompletableFuture<Suggestions> suggestCode(
|
||||
CommandContext<ServerCommandSource> context,
|
||||
SuggestionsBuilder suggestionsBuilder
|
||||
)
|
||||
) throws CommandSyntaxException
|
||||
{
|
||||
CarpetScriptHost currentHost = getHost(context);
|
||||
String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT);
|
||||
@@ -291,7 +292,7 @@ public class ScriptCommand
|
||||
)?1:0)))))).
|
||||
then(literal("remove_from").
|
||||
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()).
|
||||
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(
|
||||
@@ -311,14 +312,14 @@ public class ScriptCommand
|
||||
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))));
|
||||
}
|
||||
private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context)
|
||||
private static CarpetScriptHost getHost(CommandContext<ServerCommandSource> context) throws CommandSyntaxException
|
||||
{
|
||||
CarpetScriptHost host;
|
||||
try
|
||||
{
|
||||
String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT);
|
||||
CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost);
|
||||
host = parentHost.retrieveForExecution(context.getSource());
|
||||
host = parentHost.retrieveOwnForExecution(context.getSource());
|
||||
}
|
||||
catch (IllegalArgumentException ignored)
|
||||
{
|
||||
@@ -327,7 +328,7 @@ public class ScriptCommand
|
||||
host.setChatErrorSnooper(context.getSource());
|
||||
return host;
|
||||
}
|
||||
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c)
|
||||
private static Collection<String> suggestFunctionCalls(CommandContext<ServerCommandSource> c) throws CommandSyntaxException
|
||||
{
|
||||
CarpetScriptHost host = getHost(c);
|
||||
return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList());
|
||||
@@ -350,7 +351,7 @@ public class ScriptCommand
|
||||
}
|
||||
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);
|
||||
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
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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,
|
||||
BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode)
|
||||
BlockStateArgument block, Predicate<CachedBlockPosition> replacement, String mode) throws CommandSyntaxException
|
||||
{
|
||||
ServerCommandSource source = context.getSource();
|
||||
CarpetScriptHost host = getHost(context);
|
||||
|
||||
@@ -3,4 +3,5 @@ package carpet.fakes;
|
||||
public interface HungerManagerInterface {
|
||||
float getExhaustionCM();
|
||||
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.MinecartEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.network.packet.s2c.play.HeldItemChangeS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
|
||||
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
|
||||
{
|
||||
USE(true)
|
||||
|
||||
@@ -132,7 +132,8 @@ public class OptimizedExplosion
|
||||
|
||||
if (d12 <= 1.0D) {
|
||||
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 d13 = (double) MathHelper.sqrt(d5 * d5 + d7 * d7 + d9 * d9);
|
||||
|
||||
|
||||
@@ -8,13 +8,11 @@ import org.spongepowered.asm.mixin.Shadow;
|
||||
@Mixin(HungerManager.class)
|
||||
public class HungerManagerMixin_scarpetEventsMixin implements HungerManagerInterface {
|
||||
@Shadow private float exhaustion;
|
||||
public float getExhaustionCM(){
|
||||
return exhaustion;
|
||||
}
|
||||
|
||||
@Shadow private float foodSaturationLevel;
|
||||
@Override
|
||||
public void setExhaustionCM(float exhaust)
|
||||
{
|
||||
exhaustion = exhaust;
|
||||
}
|
||||
public float getExhaustionCM(){ return exhaustion; }
|
||||
@Override
|
||||
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();
|
||||
if(serverPlayerEntity_3 instanceof EntityPlayerMPFake)
|
||||
{
|
||||
serverPlayerEntity_3.kill();
|
||||
((EntityPlayerMPFake)serverPlayerEntity_3).kill(new TranslatableText("multiplayer.disconnect.duplicate_login"));
|
||||
continue;
|
||||
}
|
||||
serverPlayerEntity_3.networkHandler.disconnect(new TranslatableText("multiplayer.disconnect.duplicate_login"));
|
||||
|
||||
+9
-1
@@ -1,6 +1,7 @@
|
||||
package carpet.mixins;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.fakes.ServerWorldInterface;
|
||||
import carpet.script.CarpetEventServer;
|
||||
import net.minecraft.entity.Entity;
|
||||
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.profiler.Profiler;
|
||||
import net.minecraft.world.chunk.WorldChunk;
|
||||
import net.minecraft.world.level.ServerWorldProperties;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@@ -19,7 +22,7 @@ import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
|
||||
import static carpet.script.CarpetEventServer.Event.LIGHTNING;
|
||||
|
||||
@Mixin(ServerWorld.class)
|
||||
public class ServerWorld_scarpetEventMixin
|
||||
public class ServerWorld_scarpetMixin implements ServerWorldInterface
|
||||
{
|
||||
@Inject(method = "tickChunk", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(
|
||||
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 CompoundTag tag;
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.player.HungerManager;
|
||||
import net.minecraft.network.NetworkSide;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerTask;
|
||||
@@ -95,9 +96,14 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
|
||||
|
||||
@Override
|
||||
public void kill()
|
||||
{
|
||||
kill(Messenger.s("Killed"));
|
||||
}
|
||||
|
||||
public void kill(Text reason)
|
||||
{
|
||||
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);
|
||||
setHealth(20);
|
||||
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.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
|
||||
public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler
|
||||
{
|
||||
@@ -22,6 +23,10 @@ public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler
|
||||
@Override
|
||||
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.settings.ParsedRule;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.passive.MerchantEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
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.registry.Registry;
|
||||
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.chunk.Chunk;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -47,8 +51,9 @@ import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -65,59 +70,84 @@ public class CarpetEventServer
|
||||
public static class Callback
|
||||
{
|
||||
public final String host;
|
||||
public final String optionalTarget;
|
||||
public final FunctionValue function;
|
||||
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.function = function;
|
||||
this.optionalTarget = target;
|
||||
this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used also in entity events
|
||||
* @param asSource - entity command source
|
||||
* @param sender - entity command source
|
||||
* @param runtimeArgs = options
|
||||
*/
|
||||
public boolean execute(ServerCommandSource asSource, List<Value> runtimeArgs)
|
||||
public boolean execute(ServerCommandSource sender, List<Value> runtimeArgs)
|
||||
{
|
||||
if (this.parametrizedArgs.isEmpty())
|
||||
return CarpetServer.scriptServer.runas(
|
||||
asSource.withLevel(CarpetSettings.runPermissionLevel),
|
||||
host, function, runtimeArgs);
|
||||
else
|
||||
if (!this.parametrizedArgs.isEmpty())
|
||||
{
|
||||
List<Value> combinedArgs = new ArrayList<>(runtimeArgs);
|
||||
combinedArgs.addAll(this.parametrizedArgs);
|
||||
return CarpetServer.scriptServer.runas(
|
||||
asSource.withLevel(CarpetSettings.runPermissionLevel),
|
||||
host, function, combinedArgs);
|
||||
runtimeArgs = new ArrayList<>(runtimeArgs);
|
||||
runtimeArgs.addAll(this.parametrizedArgs);
|
||||
}
|
||||
return CarpetServer.scriptServer.runEventCall(
|
||||
sender.withLevel(CarpetSettings.runPermissionLevel),
|
||||
host, optionalTarget, function, runtimeArgs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
if (!this.parametrizedArgs.isEmpty())
|
||||
{
|
||||
args = new ArrayList<>(runtimeArgs);
|
||||
args.addAll(this.parametrizedArgs);
|
||||
}
|
||||
return CarpetServer.scriptServer.runForPlayer(recipient, host, function, args);
|
||||
return CarpetServer.scriptServer.signal(sender, optionalRecipient, host, function, args, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
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)
|
||||
{
|
||||
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.dueTime = dueTime;
|
||||
}
|
||||
@@ -139,7 +170,7 @@ public class CarpetEventServer
|
||||
*/
|
||||
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;
|
||||
//List<Callback> fails = new ArrayList<>();
|
||||
@@ -181,15 +212,13 @@ public class CarpetEventServer
|
||||
int successes = 0;
|
||||
for (Callback call: callList)
|
||||
{
|
||||
Boolean result = call.targetPlayer(receipient, callArg);
|
||||
//if (result == Boolean.FALSE) fails.add(call);
|
||||
if (result == Boolean.TRUE) successes++;
|
||||
successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg));
|
||||
}
|
||||
//for (Callback call : fails) callList.remove(call);
|
||||
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);
|
||||
if (host == null)
|
||||
@@ -198,11 +227,7 @@ public class CarpetEventServer
|
||||
Messenger.m(source, "r Unknown app "+hostName);
|
||||
return false;
|
||||
}
|
||||
if (!verifier.apply(host))
|
||||
{
|
||||
Messenger.m(source, "r Global event can only be added to apps with global scope");
|
||||
return false;
|
||||
}
|
||||
hostOnEventHandler.accept(host);
|
||||
FunctionValue udf = host.getFunction(funName);
|
||||
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);
|
||||
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
|
||||
//remove duplicates
|
||||
|
||||
removeEventCall(hostName, udf.getString());
|
||||
callList.add(new Callback(hostName, udf, null));
|
||||
removeEventCall(hostName, target, udf.getString());
|
||||
callList.add(new Callback(hostName, target, udf, null));
|
||||
return true;
|
||||
}
|
||||
public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List<Value> args)
|
||||
@@ -225,20 +263,39 @@ public class CarpetEventServer
|
||||
}
|
||||
//all clear
|
||||
//remove duplicates
|
||||
removeEventCall(host.getName(), function.getString());
|
||||
callList.add(new Callback(host.getName(), function, args));
|
||||
removeEventCall(host.getName(), host.user, function.getString());
|
||||
callList.add(new Callback(host.getName(), host.user, function, args));
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
callList.clear();
|
||||
@@ -255,6 +312,30 @@ public class CarpetEventServer
|
||||
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)
|
||||
{
|
||||
@Override
|
||||
@@ -470,6 +551,20 @@ public class CarpetEventServer
|
||||
), 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)
|
||||
{
|
||||
@Override
|
||||
@@ -801,6 +896,12 @@ public class CarpetEventServer
|
||||
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()
|
||||
{
|
||||
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 onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {}
|
||||
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) { }
|
||||
@@ -888,7 +990,7 @@ public class CarpetEventServer
|
||||
{
|
||||
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);
|
||||
return added;
|
||||
}
|
||||
@@ -897,8 +999,7 @@ public class CarpetEventServer
|
||||
{
|
||||
// this is globals only
|
||||
Event ev = Event.byName.get(event);
|
||||
if (!canAddEvent(ev, host))
|
||||
throw new InternalExpressionException("Global event "+event+" can only be added to apps with global scope");
|
||||
onEventAddedToHost(ev, host);
|
||||
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())));
|
||||
}
|
||||
@@ -906,22 +1007,21 @@ public class CarpetEventServer
|
||||
public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List<Value> args)
|
||||
{
|
||||
Event ev = Event.getOrCreateCustom(event, host.getScriptServer());
|
||||
if (!canAddEvent(ev, host))
|
||||
throw new InternalExpressionException("Global built-in event "+event+" can only be handled in apps with global scope");
|
||||
onEventAddedToHost(ev, host);
|
||||
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;
|
||||
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");
|
||||
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)
|
||||
@@ -932,8 +1032,8 @@ public class CarpetEventServer
|
||||
Messenger.m(source, "r Unknown event: " + event);
|
||||
return false;
|
||||
}
|
||||
Pair<String,String> call = decodeCallback(funName);
|
||||
ev.handler.removeEventCall(call.getLeft(), call.getRight());
|
||||
Callback.Signature call = Callback.fromString(funName);
|
||||
ev.handler.removeEventCall(call.host, call.target, call.function);
|
||||
// could verified if actually removed
|
||||
Messenger.m(source, "gi Removed event: " + funName + " from "+event);
|
||||
return true;
|
||||
@@ -949,13 +1049,15 @@ public class CarpetEventServer
|
||||
public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// remove event handlers
|
||||
Event.removeAllHostEvents(host);
|
||||
if (host.isPerUser())
|
||||
for (ScriptHost child: host.userHosts.values()) Event.removeAllHostEvents((CarpetScriptHost) child);
|
||||
// remove scheduled calls
|
||||
scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName()));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package carpet.script;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.script.api.Auxiliary;
|
||||
import carpet.script.argument.FunctionArgument;
|
||||
import carpet.script.bundled.Module;
|
||||
@@ -10,12 +11,12 @@ import carpet.script.exception.CarpetExpressionException;
|
||||
import carpet.script.exception.ExpressionException;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.exception.InvalidCallbackException;
|
||||
import carpet.script.value.EntityValue;
|
||||
import carpet.script.value.FunctionValue;
|
||||
import carpet.script.value.MapValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.utils.Messenger;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
@@ -38,9 +39,11 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static net.minecraft.server.command.CommandManager.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
|
||||
{
|
||||
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();
|
||||
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();
|
||||
@@ -162,8 +165,9 @@ public class CarpetScriptHost extends ScriptHost
|
||||
) throws CommandSyntaxException
|
||||
{
|
||||
String hostName = main.getName();
|
||||
Function<ServerCommandSource, Boolean> configValidator = getCommandConfigPermissions();
|
||||
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)
|
||||
{
|
||||
command = this.addPathToCommand(command, commandData.getKey(), commandData.getValue());
|
||||
@@ -171,12 +175,63 @@ public class CarpetScriptHost extends ScriptHost
|
||||
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
|
||||
protected ScriptHost duplicate()
|
||||
{
|
||||
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
|
||||
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;
|
||||
Map<Value, Value> config = ((MapValue) ret).getMap();
|
||||
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
|
||||
Value arguments = config.get(StringValue.of("arguments"));
|
||||
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)
|
||||
{
|
||||
try
|
||||
if (optionalTarget == null)
|
||||
{
|
||||
ServerPlayerEntity player = source.getPlayer();
|
||||
host = (CarpetScriptHost) retrieveForExecution(player.getName().getString());
|
||||
for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList())
|
||||
{
|
||||
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);
|
||||
return host;
|
||||
else
|
||||
{
|
||||
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 (errorSnooper == null) setChatErrorSnooper(this.scriptServer.server.getCommandSource());
|
||||
if (errorSnooper == null) setChatErrorSnooper(source);
|
||||
return this;
|
||||
}
|
||||
// user based
|
||||
CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(target.getName().getString());
|
||||
if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(target.getCommandSource());
|
||||
ServerPlayerEntity player;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -645,6 +720,12 @@ public class CarpetScriptHost extends ScriptHost
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package carpet.script;
|
||||
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.script.argument.FunctionArgument;
|
||||
import carpet.script.bundled.BundledModule;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.CarpetServer;
|
||||
import carpet.script.bundled.FileModule;
|
||||
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.exceptions.CommandSyntaxException;
|
||||
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.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -116,31 +122,42 @@ public class CarpetScriptServer
|
||||
});
|
||||
if (CarpetSettings.scriptsAutoload)
|
||||
{
|
||||
Messenger.m(server.getCommandSource(), "Auto-loading world scarpet apps");
|
||||
for (String moduleName: listAvailableModules(false))
|
||||
{
|
||||
addScriptHost(server.getCommandSource(), moduleName, null, true, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
CarpetEventServer.Event.START.onTick();
|
||||
}
|
||||
|
||||
public Module getModule(String name, boolean allowLibraries)
|
||||
{
|
||||
File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile();
|
||||
File[] listOfFiles = folder.listFiles();
|
||||
if (listOfFiles != null)
|
||||
for (File script : listOfFiles)
|
||||
try {
|
||||
Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
|
||||
Files.createDirectories(folder);
|
||||
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"))
|
||||
{
|
||||
return new FileModule(script);
|
||||
}
|
||||
if (allowLibraries && script.getName().equalsIgnoreCase(name+".scl"))
|
||||
{
|
||||
return new FileModule(script);
|
||||
}
|
||||
Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
|
||||
Files.createDirectories(globalFolder);
|
||||
scriptPath = Files.walk(globalFolder)
|
||||
.filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
|
||||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
|
||||
.findFirst();
|
||||
if (scriptPath.isPresent())
|
||||
return new FileModule(scriptPath.get());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
CarpetSettings.LOG.error("Exception while loading the app: ", e);
|
||||
}
|
||||
for (Module moduleData : bundledModuleData)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile();
|
||||
File[] listOfFiles = folder.listFiles();
|
||||
if (listOfFiles == null)
|
||||
return moduleNames;
|
||||
for (File script : listOfFiles)
|
||||
{
|
||||
if (script.getName().endsWith(".sc"))
|
||||
try {
|
||||
Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
|
||||
Files.createDirectories(worldScripts);
|
||||
Files.list(worldScripts)
|
||||
.filter(f -> f.toString().endsWith(".sc"))
|
||||
.forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
|
||||
|
||||
if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT))
|
||||
{
|
||||
String name = script.getName().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT);
|
||||
moduleNames.add(name);
|
||||
Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
|
||||
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;
|
||||
}
|
||||
@@ -198,7 +221,6 @@ public class CarpetScriptServer
|
||||
public boolean addScriptHost(ServerCommandSource source, String name, Function<ServerCommandSource, Boolean> commandValidator,
|
||||
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;
|
||||
long start = System.nanoTime();
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
if (newHost.isPerUser())
|
||||
{
|
||||
// that will provide player hosts right at the startup
|
||||
newHost.retrieveForExecution(source, null);
|
||||
}
|
||||
long end = System.nanoTime();
|
||||
CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms");
|
||||
return true;
|
||||
@@ -297,12 +324,23 @@ public class CarpetScriptServer
|
||||
return true;
|
||||
}
|
||||
|
||||
Function<ServerCommandSource, Boolean> configValidator;
|
||||
try
|
||||
{
|
||||
configValidator = host.getCommandConfigPermissions();
|
||||
}
|
||||
catch (CommandSyntaxException e)
|
||||
{
|
||||
Messenger.m(source, "rb "+e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
LiteralArgumentBuilder<ServerCommandSource> command = literal(hostName).
|
||||
requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)).
|
||||
requires((player) -> modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)).
|
||||
executes( (c) ->
|
||||
{
|
||||
Value response = modules.get(hostName).retrieveForExecution(c.getSource()).
|
||||
handleCommandLegacy(c.getSource(),"__command", null, "");
|
||||
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
|
||||
Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, "");
|
||||
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
|
||||
return (int)response.readInteger();
|
||||
});
|
||||
@@ -331,15 +369,15 @@ public class CarpetScriptServer
|
||||
then(literal(function).
|
||||
requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null).
|
||||
executes((c) -> {
|
||||
Value response = modules.get(hostName).retrieveForExecution(c.getSource()).
|
||||
handleCommandLegacy(c.getSource(), function, null, "");
|
||||
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
|
||||
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, "");
|
||||
if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString());
|
||||
return (int) response.readInteger();
|
||||
}).
|
||||
then(argument("args...", StringArgumentType.greedyString()).
|
||||
executes( (c) -> {
|
||||
Value response = modules.get(hostName).retrieveForExecution(c.getSource()).
|
||||
handleCommandLegacy(c.getSource(), function,null, StringArgumentType.getString(c, "args..."));
|
||||
CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource());
|
||||
Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args..."));
|
||||
if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString());
|
||||
return (int)response.readInteger();
|
||||
})));
|
||||
@@ -370,54 +408,84 @@ public class CarpetScriptServer
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean runas(ServerCommandSource source, String hostname, 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)
|
||||
public boolean runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List<Value> argv)
|
||||
{
|
||||
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)
|
||||
host = modules.get(hostname).retrieveForExecution(source);
|
||||
host.callUDF(origin, source, udf, argv);
|
||||
target = sender.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget);
|
||||
if (target == null) return false;
|
||||
}
|
||||
catch (NullPointerException | InvalidCallbackException npe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
int successes = signal(sender, target, hostname, udf, argv, true );
|
||||
return successes >= 0;
|
||||
}
|
||||
|
||||
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 (recipient != null) return null;
|
||||
host = globalHost;
|
||||
source = server.getCommandSource();
|
||||
if (optionalRecipient != null) return 0;
|
||||
try
|
||||
{
|
||||
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);
|
||||
if (host == null) // not applicable
|
||||
return null;
|
||||
source = (recipient == null)? server.getCommandSource() : recipient.getCommandSource();
|
||||
// getPlayer will always return nonnull cause retrieve for execution only returns hosts for existing players.
|
||||
ServerCommandSource executingSource = host.perUser
|
||||
? source.getMinecraftServer().getPlayerManager().getPlayer(host.user).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
|
||||
{
|
||||
host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv);
|
||||
}
|
||||
catch (NullPointerException | InvalidCallbackException npe)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return successes;
|
||||
}
|
||||
|
||||
public void tick()
|
||||
@@ -431,12 +499,30 @@ public class CarpetScriptServer
|
||||
|
||||
public void onClose()
|
||||
{
|
||||
CarpetEventServer.Event.SHUTDOWN.onTick();
|
||||
for (ScriptHost host : modules.values())
|
||||
{
|
||||
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
|
||||
{
|
||||
boolean perUser;
|
||||
|
||||
@@ -9,6 +9,7 @@ import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -19,7 +20,7 @@ import java.util.Map;
|
||||
|
||||
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;
|
||||
public EntityEventsGroup(Entity e)
|
||||
{
|
||||
@@ -30,37 +31,47 @@ public class EntityEventsGroup
|
||||
public void onEvent(Event type, Object... args)
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
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();
|
||||
ScriptHost host = CarpetServer.scriptServer.getHostByName(action.getKey());
|
||||
Map.Entry<Pair<String,String>, CarpetEventServer.Callback> action = iterator.next();
|
||||
Pair<String,String> key = action.getKey();
|
||||
ScriptHost host = CarpetServer.scriptServer.getHostByName(key.getLeft());
|
||||
if (host == null)
|
||||
{
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
if (key.getRight() != null)
|
||||
{
|
||||
if (entity.getServer().getPlayerManager().getPlayer(key.getRight())==null)
|
||||
{
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!type.call(action.getValue(), entity, args))
|
||||
iterator.remove();
|
||||
}
|
||||
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)
|
||||
{
|
||||
CarpetEventServer.Callback call = type.create(hostName, fun, extraargs);
|
||||
CarpetEventServer.Callback call = type.create(key, fun, extraargs);
|
||||
if (call == null)
|
||||
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
|
||||
{
|
||||
actions.computeIfAbsent(type, k -> new HashMap<>()).remove(hostName);
|
||||
actions.computeIfAbsent(type, k -> new HashMap<>()).remove(key);
|
||||
if (actions.get(type).isEmpty())
|
||||
actions.remove(type);
|
||||
}
|
||||
@@ -107,13 +118,13 @@ public class EntityEventsGroup
|
||||
argcount = args+1; // entity is not extra
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -18,13 +18,10 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class ScriptHost
|
||||
@@ -189,7 +186,13 @@ public abstract class ScriptHost
|
||||
public FunctionValue getAssertFunction(Module module, String 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;
|
||||
}
|
||||
private FunctionValue getFunction(Module module, String name)
|
||||
@@ -318,15 +321,21 @@ public abstract class ScriptHost
|
||||
ScriptHost oldUserHost = userHosts.get(user);
|
||||
if (oldUserHost != null) return oldUserHost;
|
||||
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;
|
||||
this.transferToChild(userHost);
|
||||
userHosts.put(user, 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)
|
||||
{
|
||||
System.out.println(msg+": "+exc);
|
||||
|
||||
@@ -18,6 +18,7 @@ import carpet.script.argument.FunctionArgument;
|
||||
import carpet.script.argument.Vector3Argument;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.utils.FixedCommandSource;
|
||||
import carpet.script.utils.ShapeDispatcher;
|
||||
import carpet.script.utils.WorldTools;
|
||||
import carpet.script.value.EntityValue;
|
||||
@@ -32,8 +33,8 @@ import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.command.argument.EntityAnchorArgumentType;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityType;
|
||||
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.particle.ParticleEffect;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.command.CommandOutput;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
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.ChunkPos;
|
||||
import net.minecraft.util.math.EulerAngle;
|
||||
import net.minecraft.util.math.Vec2f;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.registry.DynamicRegistryManager;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
import net.minecraft.world.World;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -87,19 +86,39 @@ import static java.lang.Math.min;
|
||||
public class Auxiliary {
|
||||
public static final String MARKER_STRING = "__scarpet_marker";
|
||||
private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
|
||||
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 file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", "");
|
||||
file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
|
||||
if (file.isEmpty())
|
||||
if (file.isEmpty() && !isFloder)
|
||||
{
|
||||
throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers");
|
||||
}
|
||||
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)
|
||||
{
|
||||
expression.addLazyFunction("sound", -1, (c, t, lv) -> {
|
||||
@@ -310,7 +329,7 @@ public class Auxiliary {
|
||||
{
|
||||
Value nameValue = lv.get(0).evalValue(c);
|
||||
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)
|
||||
{
|
||||
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");
|
||||
ServerCommandSource s = ((CarpetContext)c).s;
|
||||
MinecraftServer server = s.getMinecraftServer();
|
||||
Value res = lv.get(0).evalValue(c);
|
||||
List<ServerPlayerEntity> targets = null;
|
||||
if (lv.size() == 2)
|
||||
{
|
||||
ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res);
|
||||
if (player == null) return LazyValue.NULL;
|
||||
s = player.getCommandSource();
|
||||
List<Value> playerValues = (res instanceof ListValue)?((ListValue) res).getItems():Collections.singletonList(res);
|
||||
List<ServerPlayerEntity> playerTargets = new ArrayList<>();
|
||||
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);
|
||||
}
|
||||
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
|
||||
{
|
||||
if (s.getEntity() instanceof PlayerEntity)
|
||||
{
|
||||
Messenger.m((PlayerEntity) s.getEntity(), "w " + res.getString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Messenger.m(s, "w " + res.getString());
|
||||
}
|
||||
targets.forEach(p -> p.getCommandSource().sendFeedback(message, false));
|
||||
}
|
||||
Value finalRes = res;
|
||||
return (_c, _t) -> finalRes; // pass through for variables
|
||||
@@ -510,7 +530,7 @@ public class Auxiliary {
|
||||
if (pVal instanceof FormattedTextValue)
|
||||
title = ((FormattedTextValue) pVal).getText();
|
||||
else
|
||||
title = Text.of(pVal.getString());
|
||||
title = new LiteralText(pVal.getString());
|
||||
}
|
||||
TitleS2CPacket timesPacket;
|
||||
if (lv.size() > 3)
|
||||
@@ -544,29 +564,14 @@ public class Auxiliary {
|
||||
|
||||
expression.addLazyFunction("run", 1, (c, t, lv) -> {
|
||||
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;
|
||||
try
|
||||
{
|
||||
Value[] error = {Value.NULL};
|
||||
List<Value> output = new ArrayList<>();
|
||||
Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute(
|
||||
new ServerCommandSource(
|
||||
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));
|
||||
}
|
||||
},
|
||||
new FixedCommandSource(s, posf, error, output),
|
||||
lv.get(0).evalValue(c).getString())
|
||||
);
|
||||
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
|
||||
});
|
||||
|
||||
expression.addLazyFunction("read_file", 2, (c, t, lv) -> {
|
||||
String resource = recognizeResource(lv.get(0).evalValue(c));
|
||||
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);
|
||||
expression.addLazyFunction("list_files", 2, (c, t, lv) ->
|
||||
{
|
||||
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
|
||||
Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight());
|
||||
if (files == null) return LazyValue.NULL;
|
||||
Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
|
||||
return (cc, tt) -> ret;
|
||||
});
|
||||
|
||||
|
||||
expression.addLazyFunction("read_file", 2, (c, t, lv) ->
|
||||
{
|
||||
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
|
||||
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;
|
||||
retVal = new NBTSerializableValue(state);
|
||||
}
|
||||
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;
|
||||
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) -> {
|
||||
String resource = recognizeResource(lv.get(0).evalValue(c));
|
||||
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 = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type);
|
||||
return success?LazyValue.TRUE:LazyValue.FALSE;
|
||||
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
|
||||
return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
|
||||
? LazyValue.TRUE
|
||||
: LazyValue.FALSE;
|
||||
});
|
||||
|
||||
expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
|
||||
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
|
||||
String resource = recognizeResource(lv.get(0).evalValue(c));
|
||||
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);
|
||||
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
|
||||
|
||||
|
||||
boolean success;
|
||||
if (type.equals("nbt"))
|
||||
if (fdesc.getMiddle().equals("nbt"))
|
||||
{
|
||||
Value val = lv.get(2).evalValue(c);
|
||||
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
|
||||
? (NBTSerializableValue) val
|
||||
: new NBTSerializableValue(val.getString());
|
||||
Tag tag = tagValue.getTag();
|
||||
success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared);
|
||||
success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -860,7 +864,7 @@ public class Auxiliary {
|
||||
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;
|
||||
});
|
||||
@@ -874,7 +878,7 @@ public class Auxiliary {
|
||||
if (lv.size()>0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
shared = lv.get(1).evalValue(c).getBoolean();
|
||||
@@ -897,7 +901,7 @@ public class Auxiliary {
|
||||
if (lv.size()>1)
|
||||
{
|
||||
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)
|
||||
{
|
||||
shared = lv.get(2).evalValue(c).getBoolean();
|
||||
@@ -961,6 +965,7 @@ public class Auxiliary {
|
||||
{
|
||||
if (lv.size() == 0)
|
||||
throw new InternalExpressionException("'signal' requires at least one argument");
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer();
|
||||
String eventName = lv.get(0).evalValue(c).getString();
|
||||
// no such event yet
|
||||
@@ -975,10 +980,10 @@ public class Auxiliary {
|
||||
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;
|
||||
Value ret = new NumericValue(counts);
|
||||
return (cc, tt) -> ret;
|
||||
return (c_, t_) -> ret;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.entity.mob.MobEntity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.predicate.entity.EntityPredicates;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.Identifier;
|
||||
@@ -34,6 +35,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -183,8 +185,9 @@ public class Entities {
|
||||
expression.addLazyFunction("entity_list", 1, (c, t, lv) ->
|
||||
{
|
||||
String who = lv.get(0).evalValue(c).getString();
|
||||
EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who);
|
||||
List<Entity> entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, eDesc.filteringPredicate);
|
||||
ServerCommandSource source = ((CarpetContext)c).s;
|
||||
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()));
|
||||
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");
|
||||
String who = lv.get(0).evalValue(c).getString();
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1);
|
||||
Vec3d center = centerLocator.vec;
|
||||
if (centerLocator.fromBlock) center.add(0.5, 0.5, 0.5);
|
||||
Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1, false, true);
|
||||
|
||||
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);
|
||||
if (rangeLocator.fromBlock)
|
||||
throw new InternalExpressionException("Range of 'entity_area' cannot come from a block argument");
|
||||
Vec3d range = rangeLocator.vec;
|
||||
Box area = new Box(center, center).expand(range.x, range.y, range.z);
|
||||
EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who);
|
||||
List<? extends Entity> entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate);
|
||||
Box area = centerBox.expand(range.x, range.y, range.z);
|
||||
EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who, cc.s.getMinecraftServer());
|
||||
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()));
|
||||
return (_c, _t ) -> retval;
|
||||
});
|
||||
@@ -228,7 +241,9 @@ public class Entities {
|
||||
Value v = lv.get(0).evalValue(c);
|
||||
if (!(v instanceof EntityValue))
|
||||
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;
|
||||
if (lv.size()==2)
|
||||
retval = ((EntityValue) v).get(what, null);
|
||||
@@ -259,11 +274,13 @@ public class Entities {
|
||||
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");
|
||||
String desc = (lv.size() == 1)?lv.get(0).getString():"*";
|
||||
return EntityValue.getEntityDescriptor(desc).listValue;
|
||||
String desc = (lv.size() == 1)?lv.get(0).evalValue(c).getString():"*";
|
||||
Value ret = EntityValue.getEntityDescriptor(desc, ((CarpetContext) c).s.getMinecraftServer()).listValue;
|
||||
return (_c, _t) -> ret;
|
||||
});
|
||||
|
||||
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())
|
||||
: Collections.singletonList(entityValue.getString());
|
||||
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);
|
||||
CarpetEventServer events = ((CarpetScriptHost)c.host).getScriptServer().events;
|
||||
if (funArg.function == null)
|
||||
|
||||
@@ -12,6 +12,7 @@ import carpet.script.value.NullValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.minecraft.command.argument.ItemStackArgument;
|
||||
@@ -32,6 +33,7 @@ import net.minecraft.recipe.ShapedRecipe;
|
||||
import net.minecraft.recipe.ShapelessRecipe;
|
||||
import net.minecraft.recipe.SpecialCraftingRecipe;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.tag.TagManager;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.InvalidIdentifierException;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
@@ -40,6 +42,7 @@ import net.minecraft.util.registry.Registry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Inventories {
|
||||
public static void apply(Expression expression)
|
||||
@@ -60,6 +63,43 @@ public class Inventories {
|
||||
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) ->
|
||||
{
|
||||
CarpetContext cc = (CarpetContext)c;
|
||||
|
||||
@@ -15,6 +15,9 @@ import carpet.script.value.FormattedTextValue;
|
||||
import carpet.script.value.Value;
|
||||
import com.google.common.collect.Lists;
|
||||
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.scoreboard.Scoreboard;
|
||||
import net.minecraft.scoreboard.ServerScoreboard;
|
||||
@@ -23,9 +26,12 @@ import net.minecraft.scoreboard.ScoreboardObjective;
|
||||
import net.minecraft.scoreboard.ScoreboardPlayerScore;
|
||||
import net.minecraft.scoreboard.Team;
|
||||
import net.minecraft.scoreboard.AbstractTeam;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Formatting;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.InvalidIdentifierException;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -404,6 +410,131 @@ public class Scoreboards {
|
||||
}
|
||||
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.ChunkTicketManagerInterface;
|
||||
import carpet.fakes.ServerChunkManagerInterface;
|
||||
import carpet.fakes.ServerWorldInterface;
|
||||
import carpet.fakes.SpawnHelperInnerInterface;
|
||||
import carpet.fakes.ThreadedAnvilChunkStorageInterface;
|
||||
import carpet.helpers.FeatureGenerator;
|
||||
@@ -58,6 +59,7 @@ import net.minecraft.item.SwordItem;
|
||||
import net.minecraft.item.TridentItem;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ChunkTicket;
|
||||
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.feature.ConfiguredStructureFeature;
|
||||
import net.minecraft.world.gen.feature.StructureFeature;
|
||||
import net.minecraft.world.level.ServerWorldProperties;
|
||||
import net.minecraft.world.poi.PointOfInterest;
|
||||
import net.minecraft.world.poi.PointOfInterestStorage;
|
||||
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) ->
|
||||
{
|
||||
Value arg = lv.get(0).evalValue(c);
|
||||
@@ -713,6 +777,21 @@ public class WorldAccess {
|
||||
}
|
||||
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
|
||||
{
|
||||
if ((args.size() & 1) == 1)
|
||||
@@ -748,7 +827,7 @@ public class WorldAccess {
|
||||
{
|
||||
Clearable.clear(world.getBlockEntity(targetPos));
|
||||
boolean success = world.setBlockState(targetPos, finalSourceBlockState, 2);
|
||||
if (success && finalData != null)
|
||||
if (finalData != null)
|
||||
{
|
||||
BlockEntity be = world.getBlockEntity(targetPos);
|
||||
if (be != null)
|
||||
@@ -759,6 +838,7 @@ public class WorldAccess {
|
||||
destTag.putInt("z", targetPos.getZ());
|
||||
be.fromTag(finalSourceBlockState, destTag);
|
||||
be.markDirty();
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
result[0] = success;
|
||||
|
||||
@@ -4,9 +4,11 @@ import carpet.script.CarpetContext;
|
||||
import carpet.script.LazyValue;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.value.BlockValue;
|
||||
import carpet.script.value.EntityValue;
|
||||
import carpet.script.value.ListValue;
|
||||
import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.Value;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
|
||||
import java.util.List;
|
||||
@@ -16,7 +18,8 @@ public class Vector3Argument extends Argument
|
||||
public Vec3d vec;
|
||||
public final double yaw;
|
||||
public final double pitch;
|
||||
public boolean fromBlock;
|
||||
public boolean fromBlock = false;
|
||||
public Entity entity = null;
|
||||
private Vector3Argument(Vec3d v, int o)
|
||||
{
|
||||
super(o);
|
||||
@@ -38,13 +41,19 @@ public class Vector3Argument extends Argument
|
||||
return this;
|
||||
}
|
||||
|
||||
private Vector3Argument withEntity(Entity e)
|
||||
{
|
||||
entity = e;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
@@ -54,6 +63,11 @@ public class Vector3Argument extends Argument
|
||||
// pos + 0.5v
|
||||
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)
|
||||
{
|
||||
List<Value> args = ((ListValue) v1).getItems();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package carpet.script.bundled;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.PositionTracker;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.nbt.TagReaders;
|
||||
@@ -16,23 +18,25 @@ import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileModule extends Module
|
||||
{
|
||||
private String name;
|
||||
private String code;
|
||||
private boolean library;
|
||||
public FileModule(File sourceFile)
|
||||
public FileModule(Path sourcePath)
|
||||
{
|
||||
library = sourceFile.getName().endsWith(".scl");
|
||||
library = sourcePath.getFileName().toString().endsWith(".scl");
|
||||
try
|
||||
{
|
||||
name = sourceFile.getName().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT);
|
||||
code = new String(Files.readAllBytes(sourceFile.toPath()), StandardCharsets.UTF_8);
|
||||
name = sourcePath.getFileName().toString().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT);
|
||||
code = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8);
|
||||
}
|
||||
catch ( IOException e)
|
||||
{
|
||||
@@ -59,25 +63,40 @@ public class FileModule extends Module
|
||||
}
|
||||
|
||||
//copied private method from net.minecraft.nbt.NbtIo.read()
|
||||
// to read non-compound tags - these won't be compressed
|
||||
public static Tag read(File file)
|
||||
{
|
||||
try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file)))
|
||||
try
|
||||
{
|
||||
byte byte_1 = dataInput_1.readByte();
|
||||
if (byte_1 == 0)
|
||||
return NbtIo.readCompressed(file);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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
|
||||
@@ -86,18 +105,32 @@ public class FileModule extends Module
|
||||
File file_2 = new File(file.getAbsolutePath() + "_tmp");
|
||||
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());
|
||||
if (tag_1.getType() != 0)
|
||||
try
|
||||
{
|
||||
dataOutputStream_1.writeUTF("");
|
||||
tag_1.write(dataOutputStream_1);
|
||||
NbtIo.writeCompressed((CompoundTag) tag_1, file_2);
|
||||
}
|
||||
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_2.renameTo(file);
|
||||
@@ -143,4 +176,18 @@ public class FileModule extends Module
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ package carpet.script.bundled;
|
||||
import carpet.CarpetServer;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class Module
|
||||
{
|
||||
@@ -19,61 +21,73 @@ public abstract class Module
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Invalid file descriptor: "+file);
|
||||
return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
|
||||
}
|
||||
throw new RuntimeException("Invalid file descriptor: "+file);
|
||||
}
|
||||
|
||||
public static Tag getData(Module module, String file, boolean isShared)
|
||||
{
|
||||
File dataFile = resolveResource(module, file, "nbt", isShared);
|
||||
Path dataFile = resolveResource(module, file, "nbt", isShared);
|
||||
if (dataFile == null) return null;
|
||||
if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null;
|
||||
synchronized (writeIOSync) { return FileModule.read(dataFile); }
|
||||
if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
|
||||
synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
|
||||
}
|
||||
|
||||
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 (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false;
|
||||
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile); }
|
||||
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
|
||||
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
|
||||
}
|
||||
|
||||
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 (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false;
|
||||
synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
|
||||
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
|
||||
synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), type.equals("text"), message); }
|
||||
}
|
||||
|
||||
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;
|
||||
synchronized (writeIOSync) { return dataFile.delete(); }
|
||||
synchronized (writeIOSync) { return dataFile.toFile().delete(); }
|
||||
}
|
||||
|
||||
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.exists()) return null;
|
||||
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
|
||||
if (!dataFile.toFile().exists()) return null;
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -63,6 +63,7 @@ import net.minecraft.command.argument.RotationArgumentType;
|
||||
import net.minecraft.command.argument.ScoreHolderArgumentType;
|
||||
import net.minecraft.command.argument.ScoreboardSlotArgumentType;
|
||||
import net.minecraft.command.argument.SwizzleArgumentType;
|
||||
import net.minecraft.command.argument.TeamArgumentType;
|
||||
import net.minecraft.command.argument.TimeArgumentType;
|
||||
import net.minecraft.command.argument.UuidArgumentType;
|
||||
import net.minecraft.command.argument.Vec2ArgumentType;
|
||||
@@ -130,7 +131,6 @@ public abstract class CommandArgument
|
||||
new VanillaUnconfigurableArgument( "blockpredicate", BlockPredicateArgumentType::blockPredicate,
|
||||
(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,
|
||||
(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
|
||||
),
|
||||
// item_predicate ?? //same as item but accepts tags, not sure right now
|
||||
// slot // item_slot
|
||||
new SlotArgument(),
|
||||
new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack,
|
||||
(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,
|
||||
(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,
|
||||
(c, p) -> new NumericValue(IntegerArgumentType.getInteger(c, p)), false
|
||||
),
|
||||
@@ -437,7 +438,7 @@ public abstract class CommandArgument
|
||||
if (config.containsKey("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()
|
||||
.map(v -> caseSensitive?v.getString():(v.getString().toLowerCase(Locale.ROOT)))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
@@ -135,23 +135,25 @@ public class DataStructures {
|
||||
|
||||
expression.addFunction("range", (lv) ->
|
||||
{
|
||||
long from = 0;
|
||||
long to = 0;
|
||||
long step = 1;
|
||||
NumericValue from = Value.ZERO;
|
||||
NumericValue to;
|
||||
NumericValue step = Value.ONE;
|
||||
int argsize = lv.size();
|
||||
if (argsize == 0 || argsize > 3)
|
||||
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)
|
||||
{
|
||||
from = to;
|
||||
to = NumericValue.asNumber(lv.get(1)).getLong();
|
||||
to = NumericValue.asNumber(lv.get(1));
|
||||
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) ->
|
||||
|
||||
@@ -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;
|
||||
|
||||
import carpet.CarpetServer;
|
||||
import carpet.CarpetSettings;
|
||||
import carpet.script.CarpetContext;
|
||||
import carpet.script.CarpetScriptHost;
|
||||
import carpet.script.value.ListValue;
|
||||
@@ -11,7 +12,9 @@ import carpet.script.value.Value;
|
||||
import carpet.settings.ParsedRule;
|
||||
import carpet.settings.SettingsManager;
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
import net.minecraft.world.GameRules;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Path;
|
||||
@@ -80,6 +83,7 @@ public class SystemInfo {
|
||||
}
|
||||
return whitelist;
|
||||
});
|
||||
put("server_dev_environment", c-> new NumericValue(FabricLoader.getInstance().isDevelopmentEnvironment()));
|
||||
|
||||
put("java_max_memory", c -> new NumericValue(Runtime.getRuntime().maxMemory()));
|
||||
put("java_allocated_memory", c -> new NumericValue(Runtime.getRuntime().totalMemory()));
|
||||
@@ -115,7 +119,7 @@ public class SystemInfo {
|
||||
CarpetServer.extensions.forEach(e -> {
|
||||
SettingsManager manager = e.customSettingsManager();
|
||||
if (manager == null) return;
|
||||
|
||||
|
||||
Collection<ParsedRule<?>> extensionRules = manager.getRules();
|
||||
extensionRules.forEach(rule -> {
|
||||
carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString()));
|
||||
@@ -123,14 +127,18 @@ public class SystemInfo {
|
||||
});
|
||||
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)
|
||||
|
||||
@@ -9,6 +9,7 @@ import carpet.fakes.MobEntityInterface;
|
||||
import carpet.fakes.HungerManagerInterface;
|
||||
import carpet.fakes.ServerPlayerInteractionManagerInterface;
|
||||
import carpet.helpers.Tracer;
|
||||
import carpet.network.ServerNetworkHandler;
|
||||
import carpet.patches.EntityPlayerMPFake;
|
||||
import carpet.script.CarpetContext;
|
||||
import carpet.script.EntityEventsGroup;
|
||||
@@ -29,6 +30,7 @@ import net.minecraft.nbt.StringTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.ExperienceBarUpdateS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
|
||||
import net.minecraft.command.EntitySelector;
|
||||
import net.minecraft.command.EntitySelectorReader;
|
||||
@@ -221,10 +223,28 @@ public class EntityValue extends Value
|
||||
return entity.hashCode();
|
||||
}
|
||||
|
||||
public static EntityClassDescriptor getEntityDescriptor(String who)
|
||||
public static EntityClassDescriptor getEntityDescriptor(String who, MinecraftServer server)
|
||||
{
|
||||
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;
|
||||
//TODO add more here like search by tags, or type
|
||||
//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("mount", (e, a) -> (e.getVehicle()!=null)?new EntityValue(e.getVehicle()):Value.NULL);
|
||||
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("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_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("head_yaw", (e, a)-> {
|
||||
if (e instanceof LivingEntity)
|
||||
@@ -498,6 +531,31 @@ public class EntityValue extends Value
|
||||
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) -> {
|
||||
if (e instanceof LivingEntity)
|
||||
{
|
||||
@@ -579,6 +637,14 @@ public class EntityValue extends Value
|
||||
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("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());
|
||||
});
|
||||
|
||||
put("saturation", (e, v)-> {
|
||||
if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong());
|
||||
put("absorption", (e, v) -> {
|
||||
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) -> {
|
||||
if (e instanceof ServerPlayerEntity)
|
||||
@@ -1372,7 +1463,7 @@ public class EntityValue extends Value
|
||||
EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName);
|
||||
if (event == null)
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@ package carpet.script.value;
|
||||
import net.minecraft.nbt.StringTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.text.BaseText;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
public class FormattedTextValue extends StringValue
|
||||
@@ -14,6 +15,33 @@ public class FormattedTextValue extends StringValue
|
||||
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
|
||||
public String getString() {
|
||||
return text.getString();
|
||||
@@ -48,10 +76,16 @@ public class FormattedTextValue extends StringValue
|
||||
return StringTag.of(Text.Serializer.toJson(text));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Value add(Value o) {
|
||||
return combine(this, o);
|
||||
}
|
||||
|
||||
public String serialize()
|
||||
{
|
||||
return Text.Serializer.toJson(text);
|
||||
}
|
||||
|
||||
public static FormattedTextValue deserialize(String 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 static LazyListValue range(long from, long to, long step)
|
||||
public static LazyListValue rangeDouble(double from, double to, double step)
|
||||
{
|
||||
return new LazyListValue()
|
||||
{
|
||||
@@ -23,10 +23,10 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
|
||||
this.limit = to;
|
||||
this.stepp = step;
|
||||
}
|
||||
private long start;
|
||||
private long current;
|
||||
private long limit;
|
||||
private long stepp;
|
||||
private final double start;
|
||||
private double current;
|
||||
private final double limit;
|
||||
private final double stepp;
|
||||
@Override
|
||||
public Value next()
|
||||
{
|
||||
@@ -50,7 +50,50 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
|
||||
@Override
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.math.RoundingMode;
|
||||
import java.util.Locale;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import static java.lang.Math.signum;
|
||||
|
||||
public class NumericValue extends Value
|
||||
{
|
||||
@@ -57,6 +58,7 @@ public class NumericValue extends Value
|
||||
{
|
||||
if (value.isInfinite()) return "INFINITY";
|
||||
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
|
||||
return BigDecimal.valueOf(value).round(displayRounding).stripTrailingZeros().toPlainString();
|
||||
}
|
||||
@@ -306,4 +308,9 @@ public class NumericValue extends Value
|
||||
if (longValue != null) return new NumericValue(-longValue);
|
||||
return new NumericValue(-value);
|
||||
}
|
||||
|
||||
public boolean isInteger()
|
||||
{
|
||||
return longValue!= null || getDouble() == (double)getLong();
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,12 @@ import java.util.regex.PatternSyntaxException;
|
||||
|
||||
public abstract class Value implements Comparable<Value>, Cloneable
|
||||
{
|
||||
public static Value FALSE = new NumericValue(0);
|
||||
public static Value TRUE = new NumericValue(1);
|
||||
public static Value ZERO = FALSE;
|
||||
public static Value NULL = new NullValue();
|
||||
public static NumericValue FALSE = new NumericValue(0);
|
||||
public static NumericValue TRUE = new NumericValue(1);
|
||||
public static NumericValue ZERO = FALSE;
|
||||
public static NumericValue ONE = TRUE;
|
||||
|
||||
public static NullValue NULL = new NullValue();
|
||||
|
||||
public String boundVariable;
|
||||
|
||||
@@ -77,6 +79,10 @@ public abstract class Value implements Comparable<Value>, Cloneable
|
||||
|
||||
public Value add(Value o) {
|
||||
String lstr = this.getString();
|
||||
if (o instanceof FormattedTextValue)
|
||||
{
|
||||
return FormattedTextValue.combine(this, o);
|
||||
}
|
||||
if (lstr == null) // null
|
||||
return new StringValue(o.getString());
|
||||
String rstr = o.getString();
|
||||
|
||||
@@ -145,9 +145,8 @@ global_functions = {
|
||||
[10+half_width,10+height,10+half_width],
|
||||
0x65432100, 'buddy detection', false
|
||||
);
|
||||
[x,y,z] = pos(e);
|
||||
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);
|
||||
for (filter(buddies, _~'id' != current_id),
|
||||
visuals+=['line', global_duration, 'from', pos(e), 'to', pos(_), 'color', 0xffff00ff];
|
||||
@@ -518,9 +517,8 @@ __tick_tracker() ->
|
||||
return()
|
||||
);
|
||||
p = player();
|
||||
[px, py, pz] = pos(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(_)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,91 +1,155 @@
|
||||
import('math','_euclidean','_manhattan','_round');
|
||||
|
||||
global_display_modes = ['chat', 'line', 'centered_sphere', 'box'];
|
||||
|
||||
__config()->{
|
||||
'commands'->{
|
||||
'from <start>'->'set_start',
|
||||
'from <start> to <end>'->'_calculate',
|
||||
'to <end>'->_(end_pos)->(_calculate(null,end_pos)),
|
||||
'toggle_line'->'_toggle_line'
|
||||
'from <start> to <end>'-> _(start, end) -> (global_current_start = start; calculate(end)),
|
||||
'to <end>'-> 'calculate',
|
||||
'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' -> {
|
||||
'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_end=null;
|
||||
global_current_start = 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);
|
||||
|
||||
set_start(pos)->(
|
||||
global_start = pos;
|
||||
global_end = null;
|
||||
global_current_start = pos;
|
||||
print(player(),format('gi Initial point set to: ', 'g '+pos, __mktp(pos)))
|
||||
);
|
||||
|
||||
_toggle_line()->(
|
||||
global_show_line= !global_show_line;
|
||||
if(global_show_line,
|
||||
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)
|
||||
)
|
||||
set_mode(mode)->(
|
||||
if (global_display_modes ~ mode,
|
||||
global_current_mode = mode
|
||||
)
|
||||
);
|
||||
|
||||
__on_player_places_block(player, item_tuple, hand, block) ->(
|
||||
if(block=='brown_carpet' && system_info('world_carpet_rules'):'carpets'=='true',
|
||||
calculate(end)->(
|
||||
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];
|
||||
if(global_start==null||player~'sneaking',//wont complain for first carpet
|
||||
set_start(effective_pos),
|
||||
_calculate(null, effective_pos)
|
||||
if(global_current_start==null||player~'sneaking',//wont complain for first carpet
|
||||
set_start(effective_pos)
|
||||
, // else
|
||||
calculate(effective_pos)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
calculate(end_pos)->(
|
||||
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
|
||||
spherical = _round(_euclidean(global_start, global_end), 0.001);
|
||||
//useful for chunk distances etc.
|
||||
cylindrical = _round(_euclidean(global_start, [global_end:0, global_start:1, global_end:2]), 0.001);
|
||||
|
||||
|
||||
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')
|
||||
on_player_uses_item(player, item_tuple, hand) ->
|
||||
(
|
||||
if (item_tuple:0 == global_assist_block && hand == 'mainhand',
|
||||
if (player ~'sneaking',
|
||||
global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1);
|
||||
display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode));
|
||||
,
|
||||
if(global_display_shapes, delete(global_display_shapes, -1))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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'->{
|
||||
'center'->{'type'->'pos', 'loaded'->'true'},
|
||||
'radius'->{'type'->'int', 'suggest'->[], 'min'->0},//to avoid default suggestions
|
||||
'replacement'->{'type'->'block'},
|
||||
'replacement'->{'type'->'blockpredicate'},
|
||||
'height'->{'type'->'int', 'suggest'->[],'min'->0},
|
||||
'orientation'->{'type'->'term', 'suggest'->['x','y','z']},
|
||||
'pointing'->{'type'->'term','suggest'->['up','down']},
|
||||
@@ -32,9 +32,20 @@ __config() -> {
|
||||
|
||||
//"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)-> (
|
||||
if(block != block(x, y, z),
|
||||
without_updates( if (set([x, y, z],block),
|
||||
existing = block(x, y, z);
|
||||
if(block != existing && (replacement && _block_matches(existing, replacement) ),
|
||||
without_updates( if (set(existing,block),
|
||||
global_affected += [x, y, z];
|
||||
));
|
||||
);
|
||||
|
||||
@@ -292,3 +292,14 @@ __on_player_picks_up_item(player, 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'],
|
||||
'portal coordinates' -> ['__toggle', 'coords', 'portals'],
|
||||
'portal links' -> ['__toggle', 'links', 'portals'],
|
||||
'<shape> <radius> following <player>' -> ['display_shape', [null,0xffffffff], true],
|
||||
'<shape> <radius> at <player>' -> ['display_shape', [null,0xffffffff], false],
|
||||
'<shape> <radius> following <player> <color>' -> ['display_shape', true],
|
||||
'<shape> <radius> at <player> <color>' -> ['display_shape', false],
|
||||
'<shape> <radius> following <entities>' -> ['display_shape', [null,0xffffffff], true],
|
||||
'<shape> <radius> at <entities>' -> ['display_shape', [null,0xffffffff], false],
|
||||
'<shape> <radius> following <entities> <color>' -> ['display_shape', true],
|
||||
'<shape> <radius> at <entities> <color>' -> ['display_shape', false],
|
||||
'<shape> clear' -> 'clear_shape',
|
||||
'clear' -> 'clear',
|
||||
},
|
||||
'arguments' -> {
|
||||
'structure' -> {'type' -> 'term', 'suggest' -> [
|
||||
'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},
|
||||
'structure' -> {'type' -> 'term', 'suggest' -> plop():'structures' },
|
||||
'radius' -> {'type' -> 'int', 'min' -> 0, 'max' -> 1024, 'suggest' -> [128, 24, 32]},
|
||||
'shape' -> {'type' -> 'term', 'options' -> keys(global_shapes) },
|
||||
'color' -> {'type' -> 'teamcolor'}
|
||||
@@ -59,29 +52,30 @@ __config() ->
|
||||
};
|
||||
|
||||
|
||||
display_shape(shape, r, player, color, following) ->
|
||||
display_shape(shape, r, entities, color, following) ->
|
||||
(
|
||||
|
||||
player = player(player);
|
||||
if (!player, return());
|
||||
if (shape == 'sphere',
|
||||
shape_config = {
|
||||
'center' -> if(following, [0,0,0], pos(player)),
|
||||
'radius' -> r
|
||||
}
|
||||
, shape == 'box',
|
||||
shape_config = {
|
||||
'from' -> if(following, [-r,-r,-r], pos(player)-r),
|
||||
'to' -> if(following, [r,r,r], pos(player)+r)
|
||||
}
|
||||
);
|
||||
if (shape_config,
|
||||
if (following, shape_config:'follow' = player);
|
||||
shape_config:'fill' = color:1 - 200;
|
||||
shape_config:'color' = color:1;
|
||||
shape_config:'line' = 5;
|
||||
draw_shape(shape, 72000, shape_config);
|
||||
global_shapes:shape += shape_config;
|
||||
thicc = max(1, floor(6-length(entities)/2));
|
||||
fillc = 250 - 100/(length(entities)+1);
|
||||
for (entities,
|
||||
if (shape == 'sphere',
|
||||
shape_config = {
|
||||
'center' -> if(following, [0,0,0], pos(_)),
|
||||
'radius' -> r
|
||||
}
|
||||
, shape == 'box',
|
||||
shape_config = {
|
||||
'from' -> if(following, [-r,-r,-r], pos(_)-r),
|
||||
'to' -> if(following, [r,r,r], pos(_)+r)
|
||||
}
|
||||
);
|
||||
if (shape_config,
|
||||
if (following, shape_config:'follow' = _);
|
||||
shape_config:'fill' = color:1 - fillc;
|
||||
shape_config:'color' = color:1;
|
||||
shape_config:'line' = thicc;
|
||||
draw_shape(shape, 72000, shape_config);
|
||||
global_shapes:shape += shape_config;
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -166,11 +166,12 @@
|
||||
"PlayerInventory_scarpetEventMixin",
|
||||
"ScoreboardObjective_scarpetMixin",
|
||||
"Scoreboard_scarpetMixin",
|
||||
"TradeOutputSlot_scarpetEventMixin",
|
||||
|
||||
"ChunkTicketManager_scarpetMixin",
|
||||
"StatType_scarpetMixin",
|
||||
"PointOfInterest_scarpetMixin",
|
||||
"ServerWorld_scarpetEventMixin",
|
||||
"ServerWorld_scarpetMixin",
|
||||
"HungerManagerMixin_scarpetEventsMixin",
|
||||
|
||||
"RecipeManager_scarpetMixin",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "carpet",
|
||||
"version": "1.4.19",
|
||||
"version": "1.4.21",
|
||||
|
||||
"name": "Carpet Mod in Fabric",
|
||||
"description": "Carpet made out of fabric",
|
||||
|
||||
Reference in new issue
Block a user