Merge branch 'master' into light

This commit is contained in:
gnembon committed 2020-06-12 00:12:06 -03:00
commit ac9fcd9ece
122 files changed
+7753 -1373

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version '0.2.6-SNAPSHOT'
id 'fabric-loom' version '0.4-SNAPSHOT'
id 'maven-publish'
}
+630 -197
View File
File diff suppressed because it is too large. Load diff
+252 -19
View File
@@ -4,36 +4,112 @@ Collection of other methods that control smaller, yet still important aspects of
## Sounds
### `sound(name, pos, volume?, pitch?)`
### `sound(name, pos, volume?, pitch?, mixer?)`
Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`. `pos` can be
either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command.
Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under
optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`.
Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostile`,`neutral`, `player`, `ambient`
and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a
corresponding `playsound` command.
## Particles
### `particle(name, pos, count?. spread?, speed?, playername?)`
### `particle(name, pos, count?. spread?, speed?, player?)`
Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed`
of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle`
command on details on those options. Valid particle names are
for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`.
### `particle_line(name, pos, pos2, density?)`
### `particle_line(name, pos, pos2, density?, player?)`
Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far
apart you would want particles to appear, so `0.1` means one every 10cm.
apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only
that player will receive particles.
### `particle_rect(name, pos, pos2, density?)`
Renders a cuboid of particles between point `pos` to `pos2` with supplied density.
### `particle_box(name, pos, pos2, density?, player?)`
### `particle_rect` (deprecated)
Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is
supplied, only that player will receive particles.
## Markers
### `draw_shape(shape, duration, key?, value?, ... )`,
### `draw_shape(shape, duration, l(key?, value?, ... ))`,
### `draw_shape(shape, duration, attribute_map)`
### `draw_shape(shape_list)`
Draws a shape in the world that will expire in `duration` ticks. Other attributes of the shape should be provided as
consecutive key - value argument pairs, either as next arguments, or packed in a list, or supplied as a proper key-value
`map`. Arguments may include shared shape attributes, which are all optional, as well as shape-specific attributes, that
could be either optional or required. Shapes will draw properly on all carpet clients. Other connected players that don't
have carpet installed will still be able to see the required shapes in the form of dust particles. Replacement shapes
are not required to follow all attributes precisely, but will allow vanilla clients to receive some experience of your
apps. One of the attributes that will definitely not be honored is the duration - particles will be send once
per shape and last whatever they typically last in the game.
Shapes can be send one by one, using either of the first three invocations, or batched as a list of shape descriptors.
Batching has this benefit that they will be send possibly as one packet, limiting network overhead of
sending many small packets to draw several shapes at once.
Shapes will fail to draw and raise a runtime error if not all its required parameters
are specified and all available shapes have some parameters that are required, so make sure to have them in place:
On the client, shapes can recognize that they are being redrawn again with the same parameters, disregarding the
duration parameter. This updates the expiry on the drawn shape to the new value, instead of adding new shape in its
place. This can be used for toggling the shapes on and off that has been send previously with very large durations,
or simply refresh the shapes periodically in more dynamic applications.
Optional shared shape attributes:
* `color` - integer value indicating the main color of the shape in the form of red, green, blue and alpha components
in the form of `0xRRGGBBAA`, with the default of `-1`, so white opaque, or `0xFFFFFFFF`.
* `player` - name or player entity to send the shape to. If specified, the shapes will appear only for the specified
player, otherwise it will be send to all players in the dimension.
* `width` - line thickness, defaults to 2.0pt
* `fill` - color for the faces, defaults to no fill. Use `color` attribute format
* `follow` - entity, or player name. Shape will follow an entity instead of being static.
Follow attribute requires all positional arguments to be relative to the entity and disallow
of using entity or block as position markers. You must specify positions as a triple.
* `snap` - if `follow` is present, indicated on which axis the snapping to entity coordinates occurs, and which axis
will be treated statically, i.e. the coordinate passed in a coord triple is the actual value in the world. Default
value is `'xyz'`, meaning the shape will be drawn relatively to the entity in all three directions. Using `xz` for
instance makes so that the shape follows the entity, but stays at the same, absolute Y coordinate.
Available shapes:
* `'line'` - draws a straight line between two points
* Required attributes:
* `from` - triple coordinates, entity, or block value indicating one end of the line
* `to` - other end of the line, same format as `from`
* `'box'` - draws a box with corners in specified points
* Required attributes:
* `from` - triple coordinates, entity, or block value indicating one corner of the box
* `to` - other corner, same format as `from`
* `'sphere'`:
* Required attributes:
* `center` - center of the sphere
* `radius` - radius of the sphere
* Optional attributes:
* `level` - level of details, or grid size. The more the denser your sphere. Default level of 0, means that the
level of detail will be selected automatically based on radius.
* `'cylinder'`:
* Required attributes:
* `center` - center of the base
* `radius` - radius of the base circle
* Optional attributes:
* `axis` - cylinder direction, one of `'x'`, `'y'`, `'z'` defaults to `'y'`
* `height` - height of the cyllinder, defaults to `0`, so flat disk. Can be negative.
* `level` - level of details, see `'sphere'`.
### `create_marker(text, pos, rotation?, block?)`
Spawns a (permanent) marker entity with text or block at position. Returns that entity for further manipulations.
Unloading the app that spawned them will cause all the markers from the loaded portion of the world to be removed.
Also - if the game loads that marker in the future and the app is not loaded, it will be removed as well.
Also, if the game loads that marker in the future and the app is not loaded, it will be removed as well.
### `remove_all_markers()`
@@ -58,16 +134,127 @@ as a string value.
Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr.
### `format(components, ...)`, `format(l(components, ...))`
Creates a line of formatted text. Each component is either a string indicating formatting and text it corresponds to
or a decorator affecting the component preceding it.
Regular formatting components is a string that have the structure of:
`'<format> <text>'`, like `'gi Hi'`, which in this case indicates a grey, italicised word `'Hi'`. The space to separate the format and the text is mandatory. The format can be empty, but the space still
needs to be there otherwise the first word of the text will be used as format, which nobody wants.
Format is a list of formatting symbols indicating the format. They can be mixed and matched although color will only be
applied once. Available symbols include:
* `i` - _italic_
* `b` - **bold**
* `s` - ~~strikethrough~~
* `u` - <u>underline</u>
* `o` - obfuscated
And colors:
* `w` - White (default)
* `y` - Yellow
* `m` - Magenta (light purple)
* `r` - Red
* `c` - Cyan (aqua)
* `l` - Lime
* `t` - lighT blue
* `f` - dark grayF (weird Flex, but ok)
* `g` - Gray
* `d` - golD
* `p` - PurPle
* `n` - browN (dark red)
* `q` - turQuoise (dark aqua)
* `e` - grEEn
* `v` - naVy blue
* `k` - blaK
* `#FFAACC` - arbitrary RGB color (1.16+), hex notation. Use uppercase for A-F symbols
Decorators (listed as extra argument after the component they would affect):
* `'^<format> <text>'` - hover over tooltip text, appearing when hovering with your mouse over the text below.
* `'?<suggestion>` - command suggestion - a message that will be pasted to chat when text below it is clicked.
* `'!<message>'` - a chat message that will be executed when the text below it is clicked.
Both suggestions and messages can contain a command, which will be executed as a player that clicks it.
So far the only usecase for formatted texts is with a `print` command. Otherwise it functions like a normal
string value representing what is actually displayed on screen.
Example usages:
<pre>
print(format('rbu Error: ', 'r Stuff happened!'))
print(format('w Click ','tb [HERE]', '^di Awesome!', '!/kill', 'w \ button to win $1000'))
// the reason why I backslash the second space is that otherwise command parser may contract consecutive spaces
// not a problem in apps
</pre>
### `logger(expr)`
Prints the message to system logs, and not to chat.
### read_file(resource, type)
### delete_file(resource, type)
### write_file(resource, type, data, ...)
With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its
content, or deletes the resource.
Resource is identified by a path to the file.
A path can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred
based on the `type` of the file.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
resources is guaranteed to be isolated from other apps. Shared resources are... well, shared across all apes, meaning
they can eat of each others file, however all access to files is synchronized, and files are never left open, so
this should not lead to any access problems.
If the app's name is `'foo'`, the script location would
be `world/scripts/foo.sc`, app
specific data directory is under `world/scripts/foo.data/...`, and shared data space is under
`world/scripts/shared/...`.
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'))
To log a single line of string
Supported values for resource `type` is:
* `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
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.
<pre>
write_file('foo', 'shared_text, l('one', 'two'));
write_file('foo', 'shared_text', 'three\n', 'four\n');
write_file('foo', 'shared_raw', 'five\n', 'six\n');
read_file('foo', 'shared_text') => ['one', 'two', 'three', '', 'four', '', 'five', 'six']
</pre>
### `run(expr)`
Runs a vanilla command from the string result of the `expr` and returns its success count
<pre>
run('fill 1 1 1 10 10 10 air') -> 123 // 123 block were filled, this operation was successful 123 times out of a possible 1000 blocks volume
run('fill 1 1 1 10 10 10 air') -> 123 // 123 block were filled, this operation was successful 123 times out of a possible 1000 block volume
run('give @s stone 4') -> 1 // this operation was successful once
</pre>
@@ -76,23 +263,66 @@ run('give @s stone 4') -> 1 // this operation was successful once
Performs autosave, saves all chunks, player data, etc. Useful for programs where autosave is disabled due to
performance reasons and saves the world only on demand.
### `load_app_data(), load_app_data(file)`
### `load_app_data()`
NOTE: usages with arguments, so `load_app_data(file)` and `load_app_data(file, shared?)` are deprecated.
Use `read_file` instead.
Loads the app data associated with the app from the world /scripts folder. Without argument returns the memory
managed and buffered / throttled NBT tag. With a file name - reads explicitly a file with that name from the
scripts folder.
managed and buffered / throttled NBT tag. With a file name, reads explicitly a file with that name from the
scripts folder that belongs exclusively to the app. if `shared` is true, the file location is not exclusive
to the app anymore, but located in a shared app space.
File descriptor can contain letters, numbers and folder separator: `'/'`. Any other characters are stripped
from the name before saving/loading. Empty descriptors are invalid. Do not add file extensions to the descriptor
Function returns nbt value with the file content, or `null` if the file is missing or there were problems
with retrieving the data.
The default no-name app, via `/script run` command can only save/load file from the shared data location.
If the app's name is `'foo'`, the script location would
be `world/scripts/foo.sc`, system-managed default app data storage is in `world/scripts/foo.data.nbt`, app
specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and shared data space is under
`world/scripts/shared/bar/../baz.nbt`.
You can use app data to save non-vanilla information separately from the world and other scripts.
### `store_app_data(tag), store_app_data(tag, file)`
### `store_app_data(tag)`
Stores the app data associated with the app from the world /scripts folder. With the `file` parameter saves
immediately and with every call, without `file` parameter, it may take up to 10 seconds for the output file
Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead.
Stores the app data associated with the app from the world `/scripts` folder. With the `file` parameter saves
immediately and with every call to a specific file defined by the `file`, either in app space, or in the scripts
shared space if `shared` is true. Without `file` parameter, it may take up to 10
seconds for the output file
to sync preventing flickering in case this tag changes frequently. It will be synced when server closes.
Returns `true` if the file was saved successfully, `false` otherwise.
Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`.
### `tick_time()`
Returns game tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time
Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time.
### `world_time()`
Returns dimension-specific tick counter.
### `day_time(new_time?)`
Returns current daytime clock value. If `new_time` is specified, sets a new clock
to that value. Daytime clocks are shared between all dimensions.
### `last_tick_times()`
Returns a 100-long array of recent tick times, in milliseconds. First item on the list is the most recent tick
If called outside of the main tick (either throgh scheduled tasks, or async execution), then the first item on the
list may refer to the previous tick performance. In this case the last entry (tick 100) would refer to the most current
tick. For all intent and purpose, `last_tick_times():0` should be used as last tick execution time, but
individual tick times may vary greatly, and these need to be taken with the little grain of
averaging.
### `game_tick(mstime?)`
@@ -118,6 +348,9 @@ Returns current dimension that the script runs in.
Evaluates the expression `expr` with different dimension execution context. `smth` can be an entity,
world-localized block, so not `block('stone')`, or a string representing a dimension like:
`'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc.
### `view_distance()`
Returns the view distance of the server.
### `schedule(delay, function, args...)`
@@ -140,7 +373,7 @@ Queries in-game statistics for certain values. Categories include:
For the options of `entry`, consult your statistics page, or give it a guess.
The call will return `null` if the statistics options are incorrect, or player didn't get these in their history.
The call will return `null` if the statistics options are incorrect, or player doesn't have them in their history.
If the player encountered the statistic, or game created for him empty one, it will return a number.
Scarpet will not affect the entries of the statistics, even if it is just creating empty ones. With `null` response
it could either mean your input is wrong, or statistic has effectively a value of `0`.
it could either mean your input is wrong, or statistic effectively has a value of `0`.
+10 -6
View File
@@ -2,11 +2,15 @@
These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`.
### `scan(cx, cy, cz, dx, dy, dz, px?, py?, pz?, expr)`
### `scan(cx, cy, cz, dx, dy, dz, px?, py?, pz?, expr)`, `scan(center, range, lower_range?, expr)`
Evaluates expression over area of blocks defined by its center (`cx, cy, cz`), expanded in all directions
by `dx, dy, dz` blocks, or optionally in negative with `d` coords, and `p` coords in positive values. `expr`
receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself.
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
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,
which would cause the expression not be evaluated for their boolean value.
@@ -16,7 +20,7 @@ return value. `break` return value has no effect.
### `volume(x1, y1, z1, x2, y2, z2, expr)`
Evaluates expression for each block in the area, the same as the `scan`function, but using two opposite corners of
Evaluates expression for each block in the area, the same as the `scan` function, but using two opposite corners of
the rectangular cuboid. Any corners can be specified, its like you would do with `/fill` command.
For return value and handling `break` and `continue` statements, see `scan` function above.
@@ -32,7 +36,7 @@ for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
### `rect(cx, cy, cz, dx?, dy?, dz?, px?, py?, pz?)`
returns an iterator, just like `range` function that iterates over rectangular cubarea of blocks. If only center
point is specified, it iterates over 27 blocks. If `d` arguments are specified, expands selection of respective
point is specified, it iterates over 27 blocks. If `d` arguments are specified, expands selection by the respective
number of blocks in each direction. If `p` arguments are specified, it uses `d` for negative offset, and `p` for positive.
### `diamond(cx, cy, cz, radius?, height?)`
+113 -51
View File
@@ -70,8 +70,8 @@ set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:
Evaluates subexpression without causing updates when blocks change in the world
Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perling noise
generator. The following code causes cascading effect as blocks placed on chunk borders will cause other chunks to get
Consider following scenario: We would like to generate a bunch of terrain in a flat world following a perlin noise
generator. The following code causes a cascading effect as blocks placed on chunk borders will cause other chunks to get
loaded to full, thus generated:
<pre>
@@ -85,7 +85,7 @@ __on_chunk_generated(x, z) -> (
)
</pre>
The following addition resolves this issue, by not allowing block updates pass chunk borders:
The following addition resolves this issue, by not allowing block updates past chunk borders:
<pre>
__config() -> m(l('scope', 'global'));
@@ -102,19 +102,26 @@ __on_chunk_generated(x, z) -> (
### `place_item(item, pos, facing?, sneak?)`
Places a given item in the world like it was placed by a player. Item names are default minecraft item name,
Uses a given item in the world like it was used by a player. Item names are default minecraft item name,
less the minecraft prefix. Default facing is 'up', but there are other options: 'down', 'north', 'east', 'south',
'west', but also there are other secondary directions important for placement of blocks like stairs, doors, etc.
Try experiment with options like 'north-up' which is placed facing north with cursor pointing to the upper part of the
block, or 'up-north', which means a block placed facing up (player looking down) and placed smidge away of the block
center towards north. Optional sneak is a boolean indicating if a player would be sneaking while placing the
block - this option only affects placement of chests and scaffolding at the moment. Returns true if placement was
block - this option only affects placement of chests and scaffolding at the moment.
Works with items that have the right-click effect on the block placed, like `bone_meal` on grass or axes on logs,
but doesn't open chests / containers, so have no effect on interactive blocks, like TNT, comparators, etc.
Returns true if placement/use was
successful, false otherwise.
<pre>
place_item('stone',x,y,z) // places a stone block on x,y,z block
place_item('piston,x,y,z,'down') // places a piston facing down
place_item('carrot',x,y,z) // attempts to plant a carrot plant. Returns true if could place carrots at that position.
place_item('bone_meal',x,y,z) // attempts to bonemeal the ground.
place_item('wooden_axe',x,y,z) // attempts to strip the log.
</pre>
### `set_poi(pos, type, occupancy?)`
@@ -127,9 +134,10 @@ If type is `null`, POI at position is removed. In any case, previous POI is also
Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial
markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32.
### `set_biome(pos, biome_name)`
### `set_biome(pos, biome_name, update=true)`
changes biome at that block position.
Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed
on the clients. Biome changes can only be send to clients with the entire data from the chunk.
### `update(pos)`
@@ -145,7 +153,7 @@ Causes a random tick at position.
### `destroy(pos), destroy(pos, -1), destroy(pos, <N>), destroy(pos, tool, nbt?)`
Destroys the block like it was mined by a player. Add -1 for silk touch, and positive number for fortune level.
Destroys the block like it was mined by a player. Add -1 for silk touch, and a positive number for fortune level.
If tool is specified, and optionally its nbt, it will use that tool and will attempt to mine the block with this tool.
If called without item context, this function, unlike harvest, will affect all kinds of blocks. If called with item
in context, it will fail to break blocks that cannot be broken by a survival player.
@@ -161,28 +169,28 @@ for mining. Obviously, in this case the use of `harvest` would be much more appl
<pre>
mine(x,y,z) ->
(
p = player();
slot = p~'selected_slot';
item_tuple = inventory_get(p, slot);
if (!item_tuple, destroy(x,y,z,'air'); return()); // empty hand, just break with 'air'
l(item, count, tag) = item_tuple;
tag_back = destroy(x,y,z, item, tag);
if (tag_back == false, // failed to break the item
return(tag_back)
);
if (tag_back == true, // block broke, tool has no tag
return(tag_back)
);
if (tag_back == null, //item broke
delete(tag:'Damage');
inventory_set(p, slot, count-1, item, tag);
return(tag_back)
);
if (type(tag_back) == 'nbt', // item didn't break, here is the effective nbt
inventory_set(p, slot, count, item, tag_back);
return(tag_back)
);
print('How did we get here?');
p = player();
slot = p~'selected_slot';
item_tuple = inventory_get(p, slot);
if (!item_tuple, destroy(x,y,z,'air'); return()); // empty hand, just break with 'air'
l(item, count, tag) = item_tuple;
tag_back = destroy(x,y,z, item, tag);
if (tag_back == false, // failed to break the item
return(tag_back)
);
if (tag_back == true, // block broke, tool has no tag
return(tag_back)
);
if (tag_back == null, //item broke
delete(tag:'Damage');
inventory_set(p, slot, count-1, item, tag);
return(tag_back)
);
if (type(tag_back) == 'nbt', // item didn't break, here is the effective nbt
inventory_set(p, slot, count, item, tag_back);
return(tag_back)
);
print('How did we get here?');
)
</pre>
@@ -200,9 +208,9 @@ Returns a triple of coordinates of a specified block or entity. Technically enti
and the same can be achieved with `query(entity,'pos')`, but for simplicity `pos` allows to pass all positional objects.
<pre>
pos(block(0,5,0)) => l(0,5,0)
pos(block(0,5,0)) => l(0,5,0)
pos(players()) => l(12.3, 45.6, 32.05)
pos(block('stone')) => Error: Cannot fetch position of an unrealized block
pos(block('stone')) => Error: Cannot fetch position of an unrealized block
</pre>
### `pos_offset(pos, direction, amount?)`
@@ -260,51 +268,51 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two
### `biome(pos)`
returns biome at that block position.
Returns the biome at that block position.
### `solid(pos)`
Boolean function, true if the block is solid
Boolean function, true if the block is solid.
### `air(pos)`
Boolean function, true if a block is air.... or cave air... or void air.... or any other air they come up with.
Boolean function, true if a block is air... or cave air... or void air... or any other air they come up with.
### `liquid(pos)`
Boolean function, true if the block is liquid, or liquidlogged
Boolean function, true if the block is liquid, or waterlogged (with any liquid).
### `flammable(pos)`
Boolean function, true if the block is flammable
Boolean function, true if the block is flammable.
### `transparent(pos)`
Boolean function, true if the block is transparent
Boolean function, true if the block is transparent.
### `opacity(pos)`
Numeric, returning opacity level of a block
Numeric function, returning the opacity level of a block.
### `blocks_daylight(pos)`
Boolean function, true if the block blocks daylight
Boolean function, true if the block blocks daylight.
### `emitted_light(pos)`
Numeric, returning light level emitted from block
Numeric function, returning the light level emitted from the block.
### `light(pos)`
Integer function, returning total light level at position
Numeric function, returning the total light level at position.
### `block_light(pos)`
Integer function, returning block light at position. From torches and other light sources.
Numeric function, returning the block light at position (from torches and other light sources).
### `sky_light(pos)`
Numeric function, returning sky light at position. From the sky access.
Numeric function, returning the sky light at position (from sky access).
### `see_sky(pos)`
@@ -352,7 +360,7 @@ Boolean function, true if the block ticks randomly.
### `blocks_movement(pos)`
Boolean function, true if block at position blocks movement.
Boolean function, true if the block at position blocks movement.
### `block_sound(pos)`
@@ -478,11 +486,11 @@ Returns the map colour of a block at position. One of:
### `loaded(pos)`
Boolean function, true if the block is accessible for the game mechanics. Normally `scarpet` doesn't check if operates
on loaded area - the game will automatically load missing blocks. We see this as advantage. Vanilla `fill/clone`
on loaded area - the game will automatically load missing blocks. We see this as an advantage. Vanilla `fill/clone`
commands only check the specified corners for loadness.
To check if block is truly loaded, I mean in memory, use `generation_status(x) != null`, as chunks can still be loaded
outside of the playable area, just are not used any of the game mechanics processes.
To check if a block is truly loaded, I mean in memory, use `generation_status(x) != null`, as chunks can still be loaded
outside of the playable area, just are not used by any of the game mechanic processes.
<pre>
loaded(pos(players())) => 1
@@ -497,7 +505,7 @@ Deprecated as of scarpet 1.6, use `loaded_status(x) > 0`, or just `loaded(x)` wi
### `loaded_status(pos)`
Returns loaded status as per new 1.14 chunk ticket system, 0 for inaccessible, 1 for border chunk, 2 for ticking,
Returns loaded status as per new 1.14 chunk ticket system, 0 for inaccessible, 1 for border chunk, 2 for redstone ticking,
3 for entity ticking
### `is_chunk_generated(pos)`, `is_chunk_generated(pos, force)`
@@ -519,13 +527,41 @@ Returns generation status as per the ticket system. Can return any value from se
can only be stable in a few states: `full`, `features`, `liquid_carvers`, and `structure_starts`. Returns `null`
if the chunk is not in memory unless called with optional `true`.
### `inhabited_time(pos)`
Returns inhabited time for a chunk.
### `spawn_potential(pos)`
Returns spawn potential at a location (1.16+ only)
### `structure_eligibility(pos, ?structure, ?size_needed)`
Checks wordgen eligibility for a structure in a given chunk. If no structure is given, or `null`, then it will check
for all structures. If bounding box of the structures is also requested, it will compute size of potential
structures. This function, unlike other in the `structure*` category is not using world data nor accesses chunks
making it preferred for scoping ungenerated terrain, but it takes some compute resources to calculate the structure.
Unlike `'structure'` this will return a tentative structure location. Random factors in world generation may prevent
the actual structure from forming.
If structure is specified, it will return `null` if a chunk is not eligible, `true` if the structure should appear, or
a map with two values: `'box'` for a pair of coordinates indicating bounding box of the structure, and `'pieces'` for
list of elements of the structure (as a tuple), with its name, direction, and box coordinates of the piece.
If structure is not specified, it will return a set of structure names that are eligible, or a map with structures
as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing
should be generated there.
### `structures(pos), structures(pos, structure_name)`
Returns structure information for a given block position. Note that structure information is the same for all the
blocks from the same chunk. `structures` function can be called with a block, or a block and a structure name. In
the first case it returns a map of structures at a given position, keyed by structure name, with values indicating
the bounding box of the structure - a pair of two 3-value coords (see examples). When called with an extra structure
name, returns list of components for that structure, with their name, direction and two sets of coordinates
name, returns a map with two values, `'box'` for bounding box of the structure, and `'pieces'` for a list of
components for that structure, with their name, direction and two sets of coordinates
indicating the bounding box of the structure piece.
### `structure_references(pos), structure_references(pos, structure_name)`
@@ -540,7 +576,7 @@ then to get its bounding boxes.
Creates or removes structure information of a structure associated with a chunk of `pos`. Unlike `plop`, blocks are
not placed in the world, only structure information is set. For the game this is a fully functional structure even
if blocks are not set. To remove structure a given point is in, use `structure_references` to find where current
if blocks are not set. To remove the structure a given point is in, use `structure_references` to find where current
structure starts.
### `plop(pos, what)`
@@ -586,6 +622,13 @@ Structure list:
* `village_savanna`: Savanna, acacia village.
* `village_taiga`: Taiga, spruce village.
* `village_snowy`: Resolute, Canada.
* `nether_fossil`: Pile of bones (1.16)
* `ruined_portal`: Ruined portal, random variant.
* `bastion_remnant`: Piglin bastion, random variant for the chunk (1.16)
* `bastion_remnant_housing`: Housing units version of a piglin bastion (1.16)
* `bastion_remnant_stable`: Hoglin stables version of q piglin bastion (1.16)
* `bastion_remnant_treasure`: Treasure room version of a piglin bastion (1.16)
* `bastion_remnant_bridge` : Bridge version of a piglin bastion (1.16)
Feature list:
@@ -642,6 +685,18 @@ Feature list:
* `coral`: random coral structure. Require water to spawn.
* `sea_pickle`
* `boulder`: A rocky, mossy formation from a giant taiga biome. Doesn't update client properly, needs relogging.
* `crimson_fungus` (1.16)
* `warped_fungus` (1.16)
* `nether_sprouts` (1.16)
* `crimson_roots` (1.16)
* `warped_roots` (1.16)
* `weeping_vines` (1.16)
* `twisting_vines` (1.16)
* `basalt_pillar` (1.16)
### `reload_chunk(pos)`
Sends full chunk data to clients. Useful when lots stuff happened and you want to refresh it on the clients.
### `reset_chunk(pos)`, `reset_chunk(from_pos, to_pos)`, `reset_chunk(l(pos, ...))`
Removes and resets the chunk, all chunks in the specified area or all chunks in a list at once, removing all previous
@@ -657,3 +712,10 @@ It returns a `map` with a report indicating how many chunks were affected, and h
* `layer_count_<status>`: number of chunks for which a `<status>` generation step has been performed
* `layer_time_<status>`: cumulative time for all chunks spent on generating `<status>` step
### add_chunk_ticket(pos, type, radius)
Adds a chunk ticket at a position, which makes the game to keep the designated area centered around
`pos` with radius of `radius` loaded for a predefined amount of ticks, defined by `type`. Allowed types
are `portal`: 300 ticks, `teleport`: 5 ticks, and `unknown`: 1 tick. Radius can be from 1 to 32 ticks.
This function is tentative - will likely change when chunk ticket API is properly fleshed out.
+201 -110
View File
@@ -3,23 +3,23 @@
## Entity Selection
Entities have to be fetched before using them. Entities can also change their state between calls to the script if
game happens either in between separate calls to the programs, or if the program calls `game_tick` on its own.
In this case - entities would need to be re-fetched, or the code should account for entities getting dead.
game ticks occur either in between separate calls to the programs, or if the program calls `game_tick` on its own.
In this case - entities would need to be re-fetched, or the code should account for entities dying.
### `player(), player(type), player(name)`
With no arguments, it returns the calling player or the player closest to the caller. Note that the main context
will receive `p` variable pointing to this player. With `type` or `name` specified it will try first to match a type,
will receive `p` variable pointing to this player. With `type` or `name` specified, it will try first to match a type,
returning a list of players matching a type, and if this fails, will assume its player name query retuning player with
that name, or `null` if no player was found. With `'all'`, list of all players in the game, in all dimensions, so end
user needs to be cautious, that you might be referring to wrong blocks and entities around the player in question.
With `type = '*'` it returns all players in caller dimension, `'survival'` returns all survival and adventure players,
`'creative'` returns all creative players, `'spectating'` returns all spectating players, and `'!spectating'`,
all not-spectating players. If all fails, with `name`, the player in question, if is logged in.
all not-spectating players. If all fails, with `name`, the player in question, if he/she is logged in.
### `entity_id(uuid), entity_id(id)`
Fetching entities wither by their ID obtained via `entity ~ 'id'`, which is unique for a dimension and current world
Fetching entities either by their ID obtained via `entity ~ 'id'`, which is unique for a dimension and current world
run, or by UUID, obtained via `entity ~ 'uuid'`. It returns null if no such entity is found. Safer way to 'store'
entities between calls, as missing entities will be returning `null`. Both calls using UUID or numerical ID are `O(1)`,
but obviously using UUIDs takes more memory and compute.
@@ -38,12 +38,12 @@ selectors are available:
### `entity_area(type, cx, cy, cz, dx, dy, dz)`
Returns entities of a specified type in an area centered on `cx, cy, cz` and at most `dx, dy, dz` blocks away from
the center point. Uses same selectors as `entities_list`
the center point. Uses the same selectors as `entities_list`.
### `entity_selector(selector)`
Returns entities satisfying given vanilla entity selector. Most complex among all the methods of selecting entities,
but the most capable. Selectors are cached so should be as fast as other methods of selecting entities.
Returns entities satisifying given vanilla entity selector. Most complex among all the methods of selecting entities,
but the most capable. Selectors are cached so it should be as fast as other methods of selecting entities.
### `spawn(name, pos, nbt?)`
@@ -53,168 +53,186 @@ you get the entity back as a return value, which is swell.
## Entity Manipulation
Unlike with blocks, that use plethora of vastly different querying functions, entities are queried with `query`
function and altered via `modify` function. Type of information needed or values to be modified are different for
Unlike with blocks, that use a plethora of vastly different querying functions, entities are queried with the `query`
function and altered via the `modify` function. Type of information needed or values to be modified are different for
each call.
Using `~` (in) operator is an alias for `query`. Especially useful if a statement has no arguments,
which in this case can be radically simplified
which in this case can be radically simplified:
<pre>
query(p, 'name') <=> p ~ 'name' // much shorter and cleaner
query(p, 'holds', 'offhand') <=> p ~ l('holds', 'offhand') // not really but can be done
</pre>
### `query(e,'removed')`
### `query(e, 'removed')`
Boolean. True if the entity is removed
Boolean. True if the entity is removed.
### `query(e,'id')`
### `query(e, 'id')`
Returns numerical id of the entity. Most efficient way to keep track of entites in a script.
Ids are only unique within current game session (ids are not preserved between restarts),
and dimension (each dimension has its own ids which can overlap.
and dimension (each dimension has its own ids which can overlap).
### `query(e,'uuid')`
### `query(e, 'uuid')`
Returns UUID (unique id) of the entity. Can be used to access entities with the other vanilla commands and
Returns the UUID (unique id) of the entity. Can be used to access entities with the other vanilla commands and
remains unique regardless of the dimension, and is preserved between game restarts. Apparently players cannot be
accessed via UUID, but name instead.
accessed via UUID, but should be accessed with their name instead.
<pre>
map(entities_area('*',x,y,z,30,30,30),run('kill '+query(_,'id'))) // doesn't kill the player
</pre>
### `query(e,'pos')`
### `query(e, 'pos')`
Triple of entity position
Triple of the entity's position
### `query(e,'location')`
### `query(e, 'location')`
Quin-tuple of entity position (x, y, and z coords), and rotation (yaw, pitch)
Quin-tuple of the entity's position (x, y, and z coords), and rotation (yaw, pitch)
### `query(e,'x'), query(e,'y'), query(e,'z')`
### `query(e, 'x'), query(e, 'y'), query(e, 'z')`
Respective entity coordinate
Respective component of entity's coordinates
### `query(e,'pitch'), query(e,'yaw')`
### `query(e, 'pitch'), query(e, 'yaw')`
Pitch and Yaw or where entity is looking.
### `query(e,'look')`
### `query(e, 'look')`
Returns a 3d vector where the entity is looking.
### `query(e,'motion')`
### `query(e, 'motion')`
Triple of entity motion vector, `l(motion_x, motion_y, motion_z)`
Triple of entity's motion vector, `l(motion_x, motion_y, motion_z)`
### `query(e,'motion_x'), query(e,'motion_y'), query(e,'motion_z')`
### `query(e, 'motion_x'), query(e, 'motion_y'), query(e, 'motion_z')`
Respective component of the motion vector
Respective component of the entity's motion vector
### `query(e,'name'), query(e,'custom_name'), query(e,'type')`
### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')`
String of entity name
<pre>query(e,'name') => Leatherworker
<pre>
query(e,'name') => Leatherworker
query(e,'custom_name') => null
query(e,'type') => villager
</pre>
### `query(e,'is_riding')`
## `query(e, 'command_name')`
Boolean. True if riding another entity.
Returns a valid string to be used in commands to address an entity. Its UUID for all entities except
player, where its their name.
### `query(e,'is_ridden')`
<pre>
run('/kill ' + e~'command_name');
</pre>
Boolean. True if another entity is riding it.
### `query(e, 'is_riding')`
### `query(e,'passengers')`
Boolean, true if the entity is riding another entity.
### `query(e, 'is_ridden')`
Boolean, true if another entity is riding it.
### `query(e, 'passengers')`
List of entities riding the entity.
### `query(e,'mount')`
### `query(e, 'mount')`
Entity that `e` rides.
### `query(e,'tags')`
### `query(e, 'tags')`
List of entity tags.
List of entity's tags.
### `query(e,'has_tags',tag)`
### `query(e, 'has_tag',tag)`
Boolean, True if the entity is marked with `tag`.
Boolean, true if the entity is marked with `tag`.
### `query(e,'is_burning')`
### `query(e, 'is_burning')`
Boolean, True if the entity is burning.
Boolean, true if the entity is burning.
### `query(e,'fire')`
### `query(e, 'fire')`
Number of remaining ticks of being on fire.
### `query(e,'silent')`
### `query(e, 'silent')`
Boolean, True if the entity is silent.
Boolean, true if the entity is silent.
### `query(e,'gravity')`
### `query(e, 'gravity')`
Boolean, True if the entity is affected by gravity, like most entities do.
Boolean, true if the entity is affected by gravity, like most entities are.
### `query(e,'immune_to_fire')`
### `query(e, 'immune_to_fire')`
Boolean, True if the entity is immune to fire.
Boolean, true if the entity is immune to fire.
### `query(e,'dimension')`
### `query(e, 'dimension')`
Name of the dimension entity is in.
Name of the dimension the entity is in.
### `query(e,'height')`
### `query(e, 'height')`
Height of the entity.
Height of the entity in blocks.
### `query(e,'width')`
### `query(e, 'width')`
Width of the entity.
Width of the entity in blocks.
### `query(e,'eye_height')`
### `query(e, 'eye_height')`
Eye height of the entity.
Eye height of the entity in blocks.
### `query(e,'age')`
### `query(e, 'age')`
Age, in ticks, of the entity, i.e. how long it existed.
Age of the entity in ticks, i.e. how long it existed.
### `query(e,'breeding_age')`
### `query(e, 'breeding_age')`
Breeding age of passive entity, in ticks. If negative it it time to adulthood, if positive, breeding cooldown
Breeding age of passive entity, in ticks. If negative, time to adulthood, if positive, breeding cooldown
### `query(e,'despawn_timer')`
### `query(e, 'despawn_timer')`
For living entities - the number of ticks they fall outside of immediate player presence.
For living entities, the number of ticks they fall outside of immediate player presence.
### `query(e,'item')`
### `query(e, 'portal_cooldown')`
Number of ticks remaining until an entity can use a portal again.
### `query(e, 'portal_timer')`
Number of ticks an entity sits in a portal.
### `query(e, 'item')`
The item triple (name, count, nbt) if its an item entity, `null` otherwise
### `query(e,'count')`
### `query(e, 'count')`
Number of items in a stack from item entity, `null` otherwise
### `query(e,'pickup_delay')`
### `query(e, 'pickup_delay')`
Retrieves pickup delay timeout for an item entity, `null` otherwise
### `query(e,'is_baby')`
### `query(e, 'is_baby')`
Boolean, true if its a baby.
### `query(e,'target')`
### `query(e, 'target')`
Returns mob's attack target or null if none or not applicable.
### `query(e,'home')`
### `query(e, 'home')`
Returns creature's home position or null if none or not applicable.
@@ -229,31 +247,31 @@ Returns a pose of an entity, one of the following options
* `'crouching'`
* `'dying'`
### `query(e,'sneaking')`
### `query(e, 'sneaking')`
Boolean, true if entity is sneaking.
Boolean, true if the entity is sneaking.
### `query(e,'sprinting')`
### `query(e, 'sprinting')`
Boolean, true if entity is sprinting.
Boolean, true if the entity is sprinting.
### `query(e,'swimming')`
### `query(e, 'swimming')`
Boolean, true if entity is swimming.
Boolean, true if the entity is swimming.
### `query(e,'jumping')`
### `query(e, 'jumping')`
Boolean, true if entity is jumping.
Boolean, true if the entity is jumping.
### `query(e,'gamemode')`
### `query(e, 'gamemode')`
String with gamemode, or `null` if not a player.
### `query(e,'gamemode_id')`
### `query(e, 'gamemode_id')`
Good'ol gamemode id, or null if not a player.
### `query(e,'player_type')`
### `query(e, 'player_type')`
Returns `null` if the argument is not a player, otherwise:
@@ -265,18 +283,25 @@ Returns `null` if the argument is not a player, otherwise:
* `shadow`: any carpet-shadowed real player
* `realms`: ?
### `query(e,'ping')`
### `query(e, 'category')`
Returns a lowercase string containing the category of the entity (hostile, passive, water, ambient, misc)
### `query(e, 'team')`
Team name for entity, or `null` if no team is assigned.
### `query(e, 'ping')`
Player's ping in milliseconds, or `null` if its not a player.
### `query(e,'permission_level')`
### `query(e, 'permission_level')`
Player's permission level, or `null` if not applicable for this entity.
### `query(e,'effect',name?)`
### `query(e, 'effect', name?)`
Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short
effect name, amplifier, and remaining duration. With an argument, if the living entity has not that potion active,
effect name, amplifier, and remaining duration in ticks. With an argument, if the living entity has not that potion active,
returns `null`, otherwise return a tuple of amplifier and remaining duration.
<pre>
@@ -285,13 +310,20 @@ query(p,'effect','haste') => [0, 177]
query(p,'effect','resistance') => null
</pre>
### `query(e,'health')`
### `query(e, 'health')`
Number indicating remaining entity health, or `null` if not applicable.
### `query(e,'holds',slot?)`
### `query(e, 'hunger')`
### `query(e, 'saturation')`
### `query(e, 'exhaustion')`
Returns triple of short name, stack count, and NBT of item held in `slot`. Available options for `slot` are:
Retrieves player hunger related information. For non-players, returns `null`.
### `query(e, 'holds', slot?)`
Returns triple of short name, stack count, and NBT of item held in `slot`, or `null` if nothing or not applicable. Available options for `slot` are:
* `mainhand`
* `offhand`
@@ -302,16 +334,16 @@ Returns triple of short name, stack count, and NBT of item held in `slot`. Avail
If `slot` is not specified, it defaults to the main hand.
### `query(e,'selected_slot')`
### `query(e, 'selected_slot')`
Number indicating the selected slot of entity inventory. Currently only applicable to players.
Number indicating the selected slot of entity's inventory. Currently only applicable to players.
### `query(e,'facing', order?)`
### `query(e, 'facing', order?)`
Returns where the entity is facing. optional order (number from 0 to 5, and negative), indicating primary directions
where entity is looking at. From most prominent (order 0) to opposite (order 5, or -1)
### `query(e,'trace', reach?, options?...)`
### `query(e, 'trace', reach?, options?...)`
Returns the result of ray tracing from entity perspective, indicating what it is looking at. Default reach is 4.5
blocks (5 for creative players), and by default it traces for blocks and entities, identical to player attack tracing
@@ -323,9 +355,9 @@ entities and blocks, blocks will take over the priority even if transparent or n
Regardless of the options selected, the result could be `null` if nothing is in reach, entity, if look targets an
entity, and block value if block is in reach.
### `query(e,'nbt',path?)`
### `query(e, 'nbt', path?)`
Returns full NBT of the entity. If path is specified, it fetches only that portion of the NBT, that corresponds to the
Returns full NBT of the entity. If path is specified, it fetches only the portion of the NBT that corresponds to the
path. For specification of `path` attribute, consult vanilla `/data get entity` command.
Note that calls to `nbt` are considerably more expensive comparing to other calls in Minecraft API, and should only
@@ -337,13 +369,13 @@ type objects via `get, put, has, delete`, so try to use API calls first for that
Like with entity querying, entity modifications happen through one function. Most position and movements modifications
don't work currently on players as their position is controlled by clients.
Currently there is no ability to modify NBT directly, but you could always use `run('data modify entity`
Currently there is no ability to modify NBT directly, but you could always use `run('data modify entity ...')`
### `modify(e,'remove')`
### `modify(e, 'remove')`
Removes (not kills) entity from the game.
### `modify(e,'kill')`
### `modify(e, 'kill')`
Kills the entity.
@@ -357,7 +389,7 @@ Changes full location vector all at once.
### `modify(e, 'x', x), modify(e, 'y', y), modify(e, 'z', z)`
Moves the entity in.... one direction.
Moves the entity in one direction.
### `modify(e, 'pitch', pitch), modify(e, 'yaw', yaw)`
@@ -379,21 +411,35 @@ Sets the corresponding component of the motion vector.
Adds a vector to the motion vector. Most realistic way to apply a force to an entity.
### `modify(e, 'custom_name'), modify(e, 'custom_name', name )`
### `modify(e, 'custom_name'), modify(e, 'custom_name', name)`
Sets the custom name of the entity.
### `modify(e, 'age', number)`
Modifies entity's internal age counter. Fiddling with this will affect directly behaviours of complex
Modifies entity's internal age counter. Fiddling with this will affect directly AI behaviours of complex
entities, so use it with caution.
### `modify(e, 'pickup_delay', number)`
Sets the pickup delay for the item entity.
### `modify(e, 'breeding_age', number)`
Sets the breeding age for the animal.
### `modify(e, 'despawn_timer', number)`
Sets a custom despawn timer value.
### `modify(e, 'portal_cooldown', number)`
Sets a custom number of ticks remaining until an entity can use a portal again.
### `modify(e, 'portal_timer', number)`
Sets a custom number of ticks an entity sits in a portal.
### `modify(e, 'dismount')`
Dismounts riding entity.
@@ -412,11 +458,11 @@ Mounts on all listed entities on `e`.
### `modify(e, 'tag', tag, ? ...), modify(e, 'tag', l(tags) )`
Adds tag / tags to the entity.
Adds tag(s) to the entity.
### `modify(e, 'clear_tag', tag, ? ...), modify(e, 'clear_tag', l(tags) )`
Removes tag from entity.
Removes tag(s) from the entity.
### `modify(e, 'talk')`
@@ -429,18 +475,63 @@ If called with `false` value, it will disable AI in the mob. `true` will enable
### `modify(e, 'no_clip', boolean)`
Sets if the entity obeys any collisions, including collisions with the terrain and basic physics. Not affecting
players, since they are controlled client side
players, since they are controlled client side.
### `modify(e, 'effect', name, duration?, amplifier?, show_particles?, show_icon?)`
### `modify(e, 'effect', name?, duration?, amplifier?, show_particles?, show_icon?)`
Applies status effect to the living entity. Takes several optional parameters, which default to `0`, `true`
and `true`. If no duration is specified, or it is null or 0, the effect is removed.
and `true`. If no duration is specified, or if it's null or 0, the effect is removed. If name is not specified,
it clears all effects.
### `modify(e, 'home', null), modify(e, 'home', block, distance?), modify(e, 'home', x, y, z, distance?)`
Sets AI to stay around the home position, within `distance` blocks from it. `distance` defaults to 16 blocks.
`null` removes it. _May_ not work fully with mobs that have this AI built in, like Villagers.
### `modify(e, 'gamemode', gamemode?), modify(e, 'gamemode', gamemode_id?)`
Modifies gamemode of player to whatever string (case-insensitive) or number you put in.
* 0: survival
* 1: creative
* 2: adventure
* 3: spectator
### `modify(e, 'jumping', boolean)`
Will make the entity constantly jump if set to true, and will stop the entity from jumping if set to false.
Note that jumping parameter can be fully controlled by the entity AI, so don't expect that this will have
a permanent effect. Use `'jump'` to make an entity jump once for sure.
Requires a living entity as an argument.
### `modify(e, 'jump'))`
Will make the entity jump once.
### `modify(e, 'silent', boolean)`
Silences or unsilences the entity.
### `modify(e, 'gravity', boolean)`
Toggles gravity for the entity.
### `modify(e, 'fire', ticks)`
Will set entity on fire for `ticks` ticks. Set to 0 to extinguish.
### `modify(e, 'hunger', value)`
### `modify(e, 'saturation', value)`
### `modify(e, 'exhaustion', value)`
Modifies directly player raw hunger components. Has no effect on non-players
### `modify(e, 'add_exhaustion', value)`
adds exhaustion value to the current player exhaustion level - that's the method you probably want to use
to manipulate how much 'food' some action cost.
## Entity Events
There is a number of events that happen to entities that you can attach your own code to in the form of event handlers.
@@ -451,19 +542,19 @@ defining the callback with `entity_event`
The following events can be handled by entities.
* `'on_tick'`: executes every tick right before entity is ticked in the game. Required arguments: `entity`
* `'on_tick'`: executes every tick right before the entity is ticked in the game. Required arguments: `entity`
* `'on_death'`: executes once when a living entity dies. Required arguments: `entity, reason`
* `'on_removed'`: execute once when an entity is removed. Required arguments: `entity`
* `'on_damaged'`: executed every time a living entity is about to receive damage.
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 will attach inapplicable event to it.
when you attach inapplicable event to it.
### `entity_event(e, event, call_name, args...)`
Attaches specific function from the current package to be called upon the `event`, with extra `args` curried to the
original required arguments for the event handler
Attaches specific function from the current package to be called upon the `event`, with extra `args` carried to the
original required arguments for the event handler.
<pre>
protect_villager(entity, amount, source, source_entity, healing_player) ->
+13 -1
View File
@@ -19,7 +19,7 @@ __on_tick() // can access blocks and entities in the overworld
__on_tick_nether() // can access blocks and entities in the nether
__on_tick_ender() // can access blocks and entities in the end
__on_chunk_generated(x,z) // called after a chunk is promoted to the full chunk,
// prodiving lowest x and z coords in the chunk
// providing lowest x and z coords in the chunk
// event will not work with optifine installed in the game
__on_lightning(block, mode) // mode is `true` if lightning caused horse trap to spawn
// player specific callbacks
@@ -33,6 +33,11 @@ __on_player_interacts_with_block(player, hand, block, face, hitvec) //right cli
__on_player_places_block(player, item_tuple, hand, block) // player have just placed the block.
__on_player_interacts_with_entity(player, entity, hand)
__on_player_attacks_entity(player, entity)
__on_player_takes_damage(player, amount, source, source_entity)
__on_player_deals_damage(player, amount, entity)
__on_player_dies(player)
__on_player_respawns(player)
__on_player_changes_dimension(player, from_pos, from_dimension, to_pos, to_dimension)
__on_player_rides(player, forward, strafe, jumping, sneaking)
__on_player_jumps(player)
__on_player_deploys_elytra(player)
@@ -43,9 +48,16 @@ __on_player_starts_sprinting(player)
__on_player_stops_sprinting(player)
__on_player_drops_item(player)
__on_player_drops_stack(player)
__on_player_connects(player)
__on_player_disconnects(player, reason)
__on_statistic(player, category, event, value) // player statistic changes
</pre>
Couple special cases.
* `__on_player_changes_dimension` returns `null` as `to_pos` when player goes back to the overworld from the end
, since the respawn location of the player is not control by the teleport, or a player can see the end credits. After
the player is eligible to respawn in the overworld, `__on_player_respawns` will be triggered.
### `/script event` command
used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to
+6 -1
View File
@@ -4,7 +4,12 @@
Most functions in this category require inventory as the first argument. Inventory could be specified by an entity,
or a block, or position (three coordinates) of a potential block with inventory. Player enderchest inventory require
two arguments, keyword `'enderchest'`, followed by the player entity argument. If the entity or a block doesn't have
two arguments, keyword `'enderchest'`, followed by the player entity argument, or a single argument as a string of a
form: `'enderchest_steve'`. If your player name starts with enderchest, it can be always accessed by passing a player
entity value. If all else fails, it will try to identify first three arguments as coordinates of a block position of
a block inventory. Player inventories can also be called by their name.
If the entity or a block doesn't have
an inventory, they 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.
+5 -2
View File
@@ -17,9 +17,12 @@ scoreboard_add('counter')
scoreboard_add('lvl','level')
</pre>
### `scoreboard_remove(objective)`
### `scoreboard_remove(objective)` `scoreboard_remove(objective, key)`
Removes an objective. Returns `true` if objective has existed and has been removed.
Removes an entire objective, or an entry in the scoreboard associated with the key.
Returns `true` if objective has existed and has been removed, or previous
value of the scoreboard if players score is removed. Returns `null` if objective didn't exist, or a key was missing
for the objective.
### `scoreboard_display(place, objective)`
+1 -1
View File
@@ -276,4 +276,4 @@ global_foo = floor(rand(10));
check_foo_not_zero();
...
check_foo_not_zero() -> if(global_foo == 0, global_foo = 1)
</pre>
</pre>
+27 -5
View File
@@ -46,15 +46,22 @@ number('3bar') => null
number('2')+number('2') => 4
</pre>
### `str(expr, params? ... ), str(expr, param_list)`
### `str(expr)`,`str(expr, params? ... )`, `str(expr, param_list)`
Returns a formatted string representing expression. Accepts formatting style accepted by `String.format`.
Supported types (with `"%?"` syntax):
If called with one argument, returns string representation of such value.
Otherwise, returns a formatted string representing the expression. Arguments for formatting can either be provided as
each consecutive parameter, or as a list which then would be the only extra parameter. To format one list argument
, you can use `str(list)`, or `str('foo %s', l(list))`.
Accepts formatting style accepted by `String.format`.
Supported types (with `"%<?>"` syntax):
* `d`, `o`, `x`: integers, octal, hex
* `a`, `e`, `f`, `g`: floats
* `b`: booleans
* `s`: strings
* `%%`: '%' character
<pre>
str(null) => null
@@ -140,6 +147,18 @@ Returns true if task has completed, or false otherwise.
Evaluates `expression` synchronized with respect to the lock `lock`. Returns the value of the expression.
### `task_dock(expr)`
In a not-task (running regular code on the main game thread) it is a pass-through command. In tasks - it docks
the current thread on the main server thread and executes expression as one server offline server task.
This is especially helpful in case a task has several docking operations to perform, such as setting a block, and
it would be much more efficient to do them all at once rather then packing each block access in each own call.
Be mindful, that docking the task means that the tick execution will be delayed until the expression is evaluated.
This will synchronize your task with other tasks using `task_dock`, but if you should be using `synchronize` to
synchronize tasks without locking the main thread.
* * *
## Auxiliary functions
@@ -244,8 +263,11 @@ sleep(50)
### `time()`
Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`.
It returns a float, which has 1 microsecond precision (0.001 ms)
Returns the number of milliseconds since 'some point', like Java's `System.nanoTime()`, which varies from system to
system and from Java to Java. This measure should NOT be used to determine the current (date)time, but to measure
durations of things.
it returns a float with time in milliseconds (ms) for convenience and microsecond (μs) resolution for sanity.
<pre>
start_time = time();
@@ -85,6 +85,7 @@ tanh
task
task_completed
task_count
task_dock
task_join
task_value
throw
+12 -1
View File
@@ -1,4 +1,5 @@
air
add_chunk_ticket
biome
blast_resistance
block
@@ -13,8 +14,10 @@ brightness
chunk_tickets
create_marker
current_dimension
day_time
destroy
diamond
draw_shape
drop_item
emitted_light
entity_area
@@ -24,18 +27,21 @@ entity_list
entity_selector
escape_nbt
flammable
format
game_tick
generation_status
hardness
harvest
in_dimension
in_slime_chunk
inhabited_time
inventory_find
inventory_get
inventory_remove
inventory_set
inventory_size
item_category
last_tick_times
light
liquid
load_app_data
@@ -62,6 +68,7 @@ property
query
random_tick
rect
reload_chunk
remove_all_markers
reset_chunk
run
@@ -82,10 +89,12 @@ sky_light
solid
sound
spawn
spawn_potential
stack_limit
statistic
store_app_data
structures
structure_eligibility
structure_references
suffocates
tick_time
@@ -93,5 +102,7 @@ ticks_randomly
top
transparent
update
view_distance
volume
without_updates
without_updates
world_time
+8 -1
View File
@@ -5,6 +5,7 @@ __on_close
__on_player_attacks_entity
__on_player_breaks_block
__on_player_clicks_block
__on_player_changes_dimension
__on_player_deploys_elytra
__on_player_drops_item
__on_player_drops_stack
@@ -25,4 +26,10 @@ __on_player_wakes_up
__on_statistic
__on_tick
__on_tick_ender
__on_tick_nether
__on_tick_nether
__on_player_takes_damage
__on_player_deals_damage
__on_player_dies
__on_player_respawns
__on_player_disconnects
__on_player_connects
+16 -6
View File
@@ -36,7 +36,7 @@
print return exit throw try
task task_count task_value task_join task_completed synchronize
task task_count task_value task_join task_completed synchronize task_dock
system_variable_get system_variable_set
@@ -54,8 +54,9 @@
hardness blast_resistance top loaded suffocates power ticks_randomly update block_tick random_tick in_slime_chunk
set without_updates
blocks_movement block_sound material map_colour property block_properties block_data poi set_poi
place_item set_biome biome loaded_status generation_status chunk_tickets
structures structure_references set_structure reset_chunk
place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets
structure_eligibility structures structure_references set_structure reset_chunk reload_chunk
add_chunk_ticket
scoreboard scoreboard_add scoreboard_remove scoreboard_display
@@ -65,11 +66,13 @@
scan volume neighbours rect diamond
logger sound particle particle_line particle_rect
format logger sound particle particle_line particle_rect
run save tick_time game_tick seed current_dimension in_dimension
run save tick_time world_time day_time last_tick_times game_tick seed current_dimension in_dimension view_distance
plop harvest destroy schedule nbt create_marker remove_all_markers load_app_data store_app_data escape_nbt statistic
plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
load_app_data store_app_data escape_nbt statistic
</Keywords>
<Keywords name="Keywords3">euler pi null true false</Keywords>
<Keywords name="Keywords4">x y z p</Keywords>
@@ -83,6 +86,7 @@
__on_player_attacks_entity
__on_player_breaks_block
__on_player_clicks_block
__on_player_changes_dimension
__on_player_deploys_elytra
__on_player_drops_item
__on_player_drops_stack
@@ -104,6 +108,12 @@
__on_tick
__on_tick_ender
__on_tick_nether
__on_player_takes_damage
__on_player_deals_damage
__on_player_dies
__on_player_respawns
__on_player_disconnects
__on_player_connects
</Keywords>
<Keywords name="Keywords8">$</Keywords>
<Keywords name="Delimiters">00&apos; 01\ 02&apos; 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
+3 -3
View File
@@ -5,11 +5,11 @@ org.gradle.jvmargs=-Xmx1G
# check these on https://fabricmc.net/use
# or better: https://modmuss50.me/fabric.html
minecraft_version=1.15.2
yarn_mappings=1.15.2+build.14
loader_version=0.7.8+build.188
yarn_mappings=1.15.2+build.17
loader_version=0.8.7+build.201
# Mod Properties
mod_version = 1.3.17+v200401
mod_version = 1.3.29+v200611
maven_group = carpet
archives_base_name = fabric-carpet
Executable
+2
View File
@@ -0,0 +1,2 @@
cat docs/scarpet/language/Overview.md docs/scarpet/language/VariablesAndConstants.md docs/scarpet/language/Operators.md docs/scarpet/language/Math.md docs/scarpet/language/SystemFunctions.md docs/scarpet/language/LoopsAndHigherOrderFunctions.md docs/scarpet/language/FunctionsAndControlFlow.md docs/scarpet/language/Containers.md docs/scarpet/api/Overview.md docs/scarpet/api/BlocksAndWorldAccess.md docs/scarpet/api/BlockIterations.md docs/scarpet/api/Entities.md docs/scarpet/api/Inventories.md docs/scarpet/api/Events.md docs/scarpet/api/Scoreboard.md docs/scarpet/api/Auxiliary.md docs/scarpet/api/ScriptCommand.md > docs/scarpet/Full.md
@@ -6,6 +6,8 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import java.util.Map;
public interface CarpetExtension
{
/**
@@ -51,4 +53,6 @@ public interface CarpetExtension
default String version() {return null;}
default void registerLoggers() {}
default Map<String, String> canHasTranslations(String lang) { return null;}
}
+16 -2
View File
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Random;
import carpet.commands.*;
import carpet.network.ServerNetworkHandler;
import carpet.helpers.TickSpeed;
import carpet.logging.LoggerRegistry;
import carpet.script.CarpetScriptServer;
@@ -56,10 +57,11 @@ public class CarpetServer // static for now - easier to handle all around the co
if (sm != null) sm.attachServer(server);
e.onServerLoaded(server);
});
scriptServer = new CarpetScriptServer();
scriptServer = new CarpetScriptServer(server);
scriptServer.loadAllWorldScripts();
MobAI.resetTrackers();
LoggerRegistry.initLoggers();
//TickSpeed.reset();
}
public static void tick(MinecraftServer server)
@@ -102,29 +104,41 @@ public class CarpetServer // static for now - easier to handle all around the co
public static void onPlayerLoggedIn(ServerPlayerEntity player)
{
ServerNetworkHandler.onPlayerJoin(player);
LoggerRegistry.playerConnected(player);
extensions.forEach(e -> e.onPlayerLoggedIn(player));
}
public static void onPlayerLoggedOut(ServerPlayerEntity player)
{
ServerNetworkHandler.onPlayerLoggedOut(player);
LoggerRegistry.playerDisconnected(player);
extensions.forEach(e -> e.onPlayerLoggedOut(player));
}
public static void onServerClosed(MinecraftServer server)
{
ServerNetworkHandler.close();
currentCommandDispatcher = null;
if (scriptServer != null) scriptServer.onClose();
settingsManager.detachServer();
LoggerRegistry.stopLoggers();
extensions.forEach(e -> e.onServerClosed(server));
minecraft_server = null;
disconnect();
}
public static void registerExtensionLoggers()
{
extensions.forEach(CarpetExtension::registerLoggers);
}
public static void disconnect()
{
// this for whatever reason gets called multiple times even when joining;
TickSpeed.reset();
settingsManager.detachServer();
}
}
+111 -58
View File
@@ -4,14 +4,13 @@ import carpet.settings.ParsedRule;
import carpet.settings.Rule;
import carpet.settings.SettingsManager;
import carpet.settings.Validator;
import carpet.utils.Translations;
import carpet.utils.Messenger;
import carpet.utils.SpawnChunks;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.world.ChunkTicketType;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Unit;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.ChunkPos;
@@ -35,7 +34,7 @@ import static carpet.settings.RuleCategory.CLIENT;
@SuppressWarnings("CanBeFinal")
public class CarpetSettings
{
public static final String carpetVersion = "1.3.17+v200401";
public static final String carpetVersion = "1.3.29+v200611";
public static final Logger LOG = LogManager.getLogger();
public static boolean skipGenerationChecks = false;
public static boolean impendingFillSkipUpdates = false;
@@ -43,6 +42,31 @@ public class CarpetSettings
public static Vec3d fixedPosition = null;
public static int runPermissionLevel = 2;
private static class LanguageValidator extends Validator<String> {
@Override public String validate(ServerCommandSource source, ParsedRule<String> currentRule, String newValue, String string) {
if (currentRule.get().equals(newValue) || source == null)
{
return newValue;
}
if (!Translations.isValidLanguage(newValue))
{
Messenger.m(source, "r "+newValue+" is not a valid language");
return null;
}
CarpetSettings.language = newValue;
Translations.updateLanguage(source);
return newValue;
}
}
@Rule(
desc = "sets the language for carpet",
category = FEATURE,
options = {"none", "zh_cn", "zh_tw"},
strict = false,
validate = LanguageValidator.class
)
public static String language = "none";
@Rule(
desc = "Nether portals correctly place entities going through",
extra = "Entities shouldn't suffocate in obsidian",
@@ -71,6 +95,7 @@ public class CarpetSettings
)
public static int portalSurvivalDelay = 80;
private static class OneHourMaxDelayLimit extends Validator<Integer> {
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
return (newValue > 0 && newValue <= 72000) ? newValue : null;
@@ -236,13 +261,6 @@ public class CarpetSettings
@Rule( desc = "Pistons can push block entities, like hoppers, chests etc.", category = {EXPERIMENTAL, FEATURE} )
public static boolean movableBlockEntities = false;
/*@Rule(
desc = "Orange stained glass behaves like slime, but doesn't stick to it",
extra = "Only available for 1.14. in 1.15 use honey instead",
category = {EXPERIMENTAL, FEATURE}
)
public static boolean honeySlime = false;*/
@Rule( desc = "Saplings turn into dead shrubs in hot climates and no water access", category = FEATURE )
public static boolean desertShrubs = false;
@@ -308,7 +326,7 @@ public class CarpetSettings
)
public static String commandPerimeterInfo = "true";
@Rule(desc = "Enables /draw commands", extra = "... allows for drawing simple shapes", category = COMMAND)
@Rule(desc = "Enables /draw commands", extra = {"... allows for drawing simple shapes or","other shapes which are sorta difficult to do normally"}, category = COMMAND)
public static String commandDraw = "true";
@Rule(
@@ -356,9 +374,6 @@ public class CarpetSettings
@Rule(desc = "Pistons, Glass and Sponge can be broken faster with their appropriate tools", category = SURVIVAL)
public static boolean missingTools = false;
//@Rule(desc = "Alternative, persistent caching strategy for nether portals", category = {SURVIVAL, CREATIVE})
//public static boolean portalCaching = false; // ineffective in 1.15
@Rule(desc = "fill/clone/setblock and structure blocks cause block updates", category = CREATIVE)
public static boolean fillUpdates = true;
@@ -472,7 +487,7 @@ public class CarpetSettings
{
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string)
{
if (currentRule.get().equals(newValue))
if (currentRule.get().equals(newValue) || source == null)
{
return newValue;
}
@@ -486,8 +501,8 @@ public class CarpetSettings
if (server.isDedicated())
{
int vd = (newValue >= 2)?newValue:((DedicatedServer) server).getProperties().viewDistance;
if (vd != CarpetServer.minecraft_server.getPlayerManager().getViewDistance())
CarpetServer.minecraft_server.getPlayerManager().setViewDistance(vd);
if (vd != server.getPlayerManager().getViewDistance())
server.getPlayerManager().setViewDistance(vd);
return newValue;
}
else
@@ -509,13 +524,9 @@ public class CarpetSettings
)
public static int viewDistance = 0;
private static class DisableSpawnChunksValidator extends Validator<Boolean> {
@Override public Boolean validate(ServerCommandSource source, ParsedRule<Boolean> currentRule, Boolean newValue, String string) {
if (currentRule.get().booleanValue() == newValue.booleanValue())
{
//must been some startup thing
return newValue;
}
public static class ChangeSpawnChunksValidator extends Validator<Integer> {
public static void changeSpawnSize(int size)
{
ServerWorld overworld = CarpetServer.minecraft_server.getWorld(DimensionType.OVERWORLD);
if (overworld != null) {
ChunkPos centerChunk = new ChunkPos(new BlockPos(
@@ -523,40 +534,39 @@ public class CarpetSettings
overworld.getLevelProperties().getSpawnY(),
overworld.getLevelProperties().getSpawnZ()
));
ServerChunkManager chunkManager = (ServerChunkManager) overworld.getChunkManager();
chunkManager.removeTicket(ChunkTicketType.START, centerChunk, 11, Unit.INSTANCE);
if (!newValue)
{
chunkManager.addTicket(ChunkTicketType.START, centerChunk, 11, Unit.INSTANCE);
}
SpawnChunks.changeSpawnChunks(overworld.getChunkManager(), centerChunk, size);
}
}
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
if (source == null) return newValue;
if (newValue < 0 || newValue > 32)
{
Messenger.m(source, "r spawn chunk size has to be between 0 and 32");
return null;
}
if (currentRule.get().intValue() == newValue.intValue())
{
//must been some startup thing
return newValue;
}
if (CarpetServer.minecraft_server == null) return newValue;
ServerWorld currentOverworld = CarpetServer.minecraft_server.getWorld(DimensionType.OVERWORLD);
if (currentOverworld != null)
{
changeSpawnSize(newValue);
}
return newValue;
}
}
@Rule(
desc = "Allows spawn chunks to unload",
desc = "Changes size of spawn chunks",
extra = {"Defines new radius", "setting it to 0 - disables spawn chunks"},
category = CREATIVE,
validate = DisableSpawnChunksValidator.class
)
public static boolean disableSpawnChunks = false;
/*private static class KelpLimit extends Validator<Integer>
{
@Override public Integer validate(ServerCommandSource source, ParsedRule<Integer> currentRule, Integer newValue, String string) {
return (newValue>=0 && newValue <=25)?newValue:null;
}
@Override
public String description() { return "You must choose a value from 0 to 25. 25 and all natural kelp can grow 25 blocks, choose 0 to make all generated kelp not to grow";}
}
@Rule(
desc = "limits growth limit of newly naturally generated kelp to this amount of blocks",
options = {"0", "2", "25"},
category = FEATURE,
strict = false,
validate = KelpLimit.class
options = {"0", "11"},
validate = ChangeSpawnChunksValidator.class
)
public static int kelpGenerationGrowthLimit = 25;*/
public static int spawnChunksSize = 11;
@Rule(desc = "Coral structures will grow with bonemeal from coral plants", category = FEATURE)
public static boolean renewableCoral = false;
@@ -573,13 +583,6 @@ public class CarpetSettings
@Rule(desc = "Spawning requires much less CPU and Memory", category = OPTIMIZATION)
public static boolean lagFreeSpawning = false;
//@Rule(
// desc = "Prevents horses and other mobs to wander into the distance after dismounting",
// extra = "Fixes issues with various Joergens wandering off and disappearing client-side",
// category = BUGFIX
//)
//public static boolean horseWanderingFix = false;
@Rule(
desc = "Allows structure mobs to spawn in flat worlds",
@@ -599,4 +602,54 @@ public class CarpetSettings
category = CREATIVE
)
public static boolean extremeBehaviours = false;
@Rule(
desc = "Removes fog from client in the nether and the end",
extra = "Improves visibility, but looks weird",
category = CLIENT
)
public static boolean fogOff = false;
@Rule(
desc = "Creative No Clip",
extra = {
"On servers it needs to be set on both ",
"client and server to function properly.",
"Has no effect when set on the server only",
"Can allow to phase through walls",
"if only set on the carpet client side",
"but requires some trapdoor magic to",
"allow the player to enter blocks"
},
category = {CREATIVE, CLIENT}
)
public static boolean creativeNoClip = false;
@Rule(
desc = "Creative flying speed multiplier",
extra = {
"Purely client side setting, meaning that",
"having it set on the decicated server has no effect",
"but this also means it will work on vanilla servers as well"
},
category = {CREATIVE, CLIENT},
validate = Validator.NONNEGATIVE_NUMBER.class
)
public static double creativeFlySpeed = 1.0;
@Rule(
desc = "Creative air drag",
extra = {
"Increased drag will slow down your flight",
"So need to adjust speed accordingly",
"With 1.0 drag, using speed of 11 seems to matching vanilla speeds.",
"Purely client side setting, meaning that",
"having it set on the decicated server has no effect",
"but this also means it will work on vanilla servers as well"
},
category = {CREATIVE, CLIENT},
validate = Validator.PROBABILITY.class
)
public static double creativeFlyDrag = 0.09;
}
+359 -68
View File
@@ -7,6 +7,11 @@ import com.google.common.collect.Lists;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.block.Block;
import net.minecraft.block.pattern.CachedBlockPosition;
import net.minecraft.server.command.ServerCommandSource;
@@ -18,12 +23,15 @@ import net.minecraft.inventory.Inventory;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.MathHelper;
import java.util.List;
import java.util.function.Predicate;
import java.lang.Math;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import static net.minecraft.server.command.CommandSource.suggestMatching;
public class DrawCommand
{
@@ -32,45 +40,142 @@ public class DrawCommand
LiteralArgumentBuilder<ServerCommandSource> command = literal("draw").
requires((player) -> SettingsManager.canUseCommand(player, CarpetSettings.commandDraw)).
then(literal("sphere").
then(argument("center",BlockPosArgumentType.blockPos()).
then(argument("radius",IntegerArgumentType.integer(1)).
then(argument("block",BlockStateArgumentType.blockState()).
executes((c)-> drawCircle(
c.getSource(),
BlockPosArgumentType.getBlockPos(c, "center"),
IntegerArgumentType.getInteger(c, "radius"),
BlockStateArgumentType.getBlockState (c,"block"),
null
)
).
then(literal("replace").
then(argument("filter",BlockPredicateArgumentType.blockPredicate())
.executes((c)-> drawCircle(
c.getSource(),
BlockPosArgumentType.getBlockPos(c, "center"),
IntegerArgumentType.getInteger(c, "radius"),
BlockStateArgumentType.getBlockState(c,"block"),
BlockPredicateArgumentType.getBlockPredicate(c,
"filter")
))))))));
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(drawShape(c -> DrawCommand.drawSphere(c, false)))))).
then(literal("ball").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(drawShape(c -> DrawCommand.drawSphere(c, true)))))).
then(literal("diamond").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(drawShape(c -> DrawCommand.drawDiamond(c, true)))))).
then(literal("pyramid").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(argument("height",IntegerArgumentType.integer(1)).
then(argument("pointing",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"up","down"},b)).
then(argument("orientation",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"y","x","z"},b)).
then(drawShape(c -> DrawCommand.drawPyramid(c, "square", true))))))))).
then(literal("cone").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(argument("height",IntegerArgumentType.integer(1)).
then(argument("pointing",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"up","down"},b)).
then(argument("orientation",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"y","x","z"},b))
.then(drawShape(c -> DrawCommand.drawPyramid(c, "circle", true))))))))).
then(literal("cylinder").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(argument("height",IntegerArgumentType.integer(1)).
then(argument("orientation",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"y","x","z"},b))
.then(drawShape(c -> DrawCommand.drawPrism(c, "circle")))))))).
then(literal("cuboid").
then(argument("center", BlockPosArgumentType.blockPos()).
then(argument("radius", IntegerArgumentType.integer(1)).
then(argument("height",IntegerArgumentType.integer(1)).
then(argument("orientation",StringArgumentType.word()).suggests( (c, b) -> suggestMatching(new String[]{"y","x","z"},b))
.then(drawShape(c -> DrawCommand.drawPrism(c, "square"))))))));
dispatcher.register(command);
}
private static int drawCircle(ServerCommandSource source, BlockPos pos, int radius, BlockStateArgument block,
Predicate<CachedBlockPosition> replacement)
@FunctionalInterface
private interface OptionalBlockSelector
{
return drawCircle(source, pos, (double)radius, (double)radius, (double)radius, block, replacement, false);
Integer apply(final CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException;
}
private static int drawCircle(ServerCommandSource source, BlockPos pos, double radiusX, double radiusY, double radiusZ,
BlockStateArgument block, Predicate<CachedBlockPosition> replacement, boolean solid)
{
int affected = 0;
ServerWorld world = source.getWorld();
radiusX += 0.5;
radiusY += 0.5;
radiusZ += 0.5;
@FunctionalInterface
private interface ArgumentExtractor<T>
{
T apply(final CommandContext<ServerCommandSource> ctx, final String argName) throws CommandSyntaxException;
}
private static RequiredArgumentBuilder<ServerCommandSource, BlockStateArgument>
drawShape(OptionalBlockSelector drawer)
{
return argument("block", BlockStateArgumentType.blockState()).
executes(drawer::apply)
.then(literal("replace")
.then(argument("filter", BlockPredicateArgumentType.blockPredicate())
.executes(drawer::apply)));
}
private static class ErrorHandled extends RuntimeException {}
private static <T> T getArg(CommandContext<ServerCommandSource> ctx, ArgumentExtractor<T> extract, String hwat) throws CommandSyntaxException
{
return getArg(ctx, extract, hwat, false);
}
private static <T> T getArg(CommandContext<ServerCommandSource> ctx, ArgumentExtractor<T> extract, String hwat, boolean optional) throws CommandSyntaxException
{
T arg = null;
try
{
arg = extract.apply(ctx, hwat);
}
catch (IllegalArgumentException e)
{
if (optional) return null;
Messenger.m(ctx.getSource(), "rb Missing "+hwat);
throw new ErrorHandled();
}
return arg;
}
private static double lengthSq(double x, double y, double z)
{
return (x * x) + (y * y) + (z * z);
}
private static int setBlock(
ServerWorld world, BlockPos.Mutable mbpos, int x, int y, int z,
BlockStateArgument block, Predicate<CachedBlockPosition> replacement,
List<BlockPos> list
)
{
mbpos.set(x, y, z);
int success=0;
if (replacement == null || replacement.test(new CachedBlockPosition(world, mbpos, true)))
{
BlockEntity tileentity = world.getBlockEntity(mbpos);
if (tileentity instanceof Inventory)
{
((Inventory) tileentity).clear();
}
if (block.setBlockState(world, mbpos, 2))
{
list.add(mbpos.toImmutable());
++success;
}
}
return success;
}
private static int drawSphere(CommandContext<ServerCommandSource> ctx, boolean solid) throws CommandSyntaxException
{
BlockPos pos;
int radius;
BlockStateArgument block;
Predicate<CachedBlockPosition> replacement;
try
{
pos = getArg(ctx, BlockPosArgumentType::getBlockPos, "center");
radius = getArg(ctx, IntegerArgumentType::getInteger, "radius");
block = getArg(ctx, BlockStateArgumentType::getBlockState, "block");
replacement = getArg(ctx, BlockPredicateArgumentType::getBlockPredicate, "filter", true);
}
catch (ErrorHandled ignored) { return 0; }
int affected = 0;
ServerWorld world = ctx.getSource().getWorld();
double radiusX = radius+0.5;
double radiusY = radius+0.5;
double radiusZ = radius+0.5;
final double invRadiusX = 1 / radiusX;
final double invRadiusY = 1 / radiusY;
@@ -85,22 +190,28 @@ public class DrawCommand
double nextXn = 0;
forX: for (int x = 0; x <= ceilRadiusX; ++x) {
forX: for (int x = 0; x <= ceilRadiusX; ++x)
{
final double xn = nextXn;
nextXn = (x + 1) * invRadiusX;
double nextYn = 0;
forY: for (int y = 0; y <= ceilRadiusY; ++y) {
forY: for (int y = 0; y <= ceilRadiusY; ++y)
{
final double yn = nextYn;
nextYn = (y + 1) * invRadiusY;
double nextZn = 0;
forZ: for (int z = 0; z <= ceilRadiusZ; ++z) {
forZ: for (int z = 0; z <= ceilRadiusZ; ++z)
{
final double zn = nextZn;
nextZn = (z + 1) * invRadiusZ;
double distanceSq = lengthSq(xn, yn, zn);
if (distanceSq > 1) {
if (z == 0) {
if (y == 0) {
if (distanceSq > 1)
{
if (z == 0)
{
if (y == 0)
{
break forX;
}
break forY;
@@ -108,35 +219,22 @@ public class DrawCommand
break forZ;
}
if (!solid) {
if (lengthSq(nextXn, yn, zn) <= 1 && lengthSq(xn, nextYn, zn) <= 1 && lengthSq(xn, yn, nextZn) <= 1) {
continue;
}
if (!solid && lengthSq(nextXn, yn, zn) <= 1 && lengthSq(xn, nextYn, zn) <= 1 && lengthSq(xn, yn, nextZn) <= 1)
{
continue;
}
CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
for (int xmod = -1; xmod < 2; xmod+= 2)
for (int xmod = -1; xmod < 2; xmod += 2)
{
for (int ymod = -1; ymod < 2; ymod += 2)
{
for (int zmod = -1; zmod < 2; zmod += 2)
{
mbpos.set(pos.getX()+xmod*x, pos.getY()+ymod*y, pos.getZ()+zmod*z);
if (replacement == null || replacement.test(
new CachedBlockPosition( world, mbpos, true)))
{
BlockEntity tileentity = world.getBlockEntity(mbpos);
if (tileentity instanceof Inventory)
{
((Inventory)tileentity).clear();
}
if (block.setBlockState(world, mbpos,2))
{
list.add(mbpos.toImmutable());
++affected;
}
}
affected+= setBlock(world, mbpos,
pos.getX() + xmod * x, pos.getY() + ymod * y, pos.getZ() + zmod * z,
block, replacement, list
);
}
}
}
@@ -146,19 +244,212 @@ public class DrawCommand
}
if (CarpetSettings.fillUpdates)
{
list.forEach(blockpos1 -> world.updateNeighbors(blockpos1, world.getBlockState(blockpos1).getBlock()));
}
Messenger.m(ctx.getSource(), "gi Filled " + affected + " blocks");
return affected;
}
for (BlockPos blockpos1 : list)
private static int drawDiamond(CommandContext<ServerCommandSource> ctx, boolean solid) throws CommandSyntaxException
{
BlockPos pos;
int radius;
BlockStateArgument block;
Predicate<CachedBlockPosition> replacement;
try
{
pos = getArg(ctx, BlockPosArgumentType::getBlockPos, "center");
radius = getArg(ctx, IntegerArgumentType::getInteger, "radius");
block = getArg(ctx, BlockStateArgumentType::getBlockState, "block");
replacement = getArg(ctx, BlockPredicateArgumentType::getBlockPredicate, "filter", true);
}
catch (ErrorHandled ignored) { return 0; }
ServerCommandSource source = ctx.getSource();
int affected=0;
BlockPos.Mutable mbpos = new BlockPos.Mutable(pos);
List<BlockPos> list = Lists.newArrayList();
ServerWorld world = source.getWorld();
CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
for (int r = 0; r < radius; ++r)
{
int y=r-radius+1;
for (int x = -r; x <= r; ++x)
{
Block blokc = world.getBlockState(blockpos1).getBlock();
world.updateNeighbors(blockpos1, blokc); // or always version????
int z=r-Math.abs(x);
affected+= setBlock(world, mbpos, pos.getX()+x, pos.getY()-y, pos.getZ()+z, block, replacement, list);
affected+= setBlock(world, mbpos, pos.getX()+x, pos.getY()-y, pos.getZ()-z, block, replacement, list);
affected+= setBlock(world, mbpos, pos.getX()+x, pos.getY()+y, pos.getZ()+z, block, replacement, list);
affected+= setBlock(world, mbpos, pos.getX()+x, pos.getY()+y, pos.getZ()-z, block, replacement, list);
}
}
Messenger.m(source, "gi Filled "+affected+" blocks");
return 1;
}
private static double lengthSq(double x, double y, double z)
CarpetSettings.impendingFillSkipUpdates = false;
if (CarpetSettings.fillUpdates)
{
return (x * x) + (y * y) + (z * z);
list.forEach(p -> world.updateNeighbors(p, world.getBlockState(p).getBlock()));
}
Messenger.m(source, "gi Filled " + affected + " blocks");
return affected;
}
private static int fillFlat(
ServerWorld world, BlockPos pos, int offset, double dr, boolean rectangle, String orientation,
BlockStateArgument block, Predicate<CachedBlockPosition> replacement,
List<BlockPos> list, BlockPos.Mutable mbpos
)
{
int successes=0;
int r = MathHelper.floor(dr);
double drsq = dr*dr;
if (orientation.equalsIgnoreCase("x"))
{
for(int a=-r; a<=r; ++a) for(int b=-r; b<=r; ++b) if(rectangle || a*a + b*b <= drsq)
{
successes += setBlock(
world, mbpos,pos.getX()+offset, pos.getY()+a, pos.getZ()+b,
block, replacement, list
);
}
return successes;
}
if (orientation.equalsIgnoreCase("y"))
{
for(int a=-r; a<=r; ++a) for(int b=-r; b<=r; ++b) if(rectangle || a*a + b*b <= drsq)
{
successes += setBlock(
world, mbpos,pos.getX()+a, pos.getY()+offset, pos.getZ()+b,
block, replacement, list
);
}
return successes;
}
if (orientation.equalsIgnoreCase("z"))
{
for(int a=-r; a<=r; ++a) for(int b=-r; b<=r; ++b) if(rectangle || a*a + b*b <= drsq)
{
successes += setBlock(
world, mbpos,pos.getX()+b, pos.getY()+a, pos.getZ()+offset,
block, replacement, list
);
}
return successes;
}
return 0;
}
private static int drawPyramid(CommandContext<ServerCommandSource> ctx, String base, boolean solid) throws CommandSyntaxException
{
BlockPos pos;
double radius;
int height;
boolean pointup;
String orientation;
BlockStateArgument block;
Predicate<CachedBlockPosition> replacement;
try
{
pos = getArg(ctx, BlockPosArgumentType::getBlockPos, "center");
radius = getArg(ctx, IntegerArgumentType::getInteger, "radius")+0.5D;
height = getArg(ctx, IntegerArgumentType::getInteger, "height");
pointup = getArg(ctx, StringArgumentType::getString, "pointing").equalsIgnoreCase("up");
orientation = getArg(ctx, StringArgumentType::getString,"orientation");
block = getArg(ctx, BlockStateArgumentType::getBlockState, "block");
replacement = getArg(ctx, BlockPredicateArgumentType::getBlockPredicate, "filter", true);
}
catch (ErrorHandled ignored) { return 0; }
ServerCommandSource source = ctx.getSource();
int affected = 0;
BlockPos.Mutable mbpos = new BlockPos.Mutable(pos);
List<BlockPos> list = Lists.newArrayList();
ServerWorld world = source.getWorld();
CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
boolean isSquare = base.equalsIgnoreCase("square");
for(int i =0; i<height;++i)
{
double r = pointup ? radius - radius * i / height - 1 : radius * i / height;
affected+= fillFlat(world, pos, i, r, isSquare, orientation, block, replacement, list, mbpos);
}
CarpetSettings.impendingFillSkipUpdates = false;
if (CarpetSettings.fillUpdates) {
for (BlockPos blockpos1 : list) {
Block blokc = world.getBlockState(blockpos1).getBlock();
world.updateNeighbors(blockpos1, blokc);
}
}
Messenger.m(source, "gi Filled " + affected + " blocks");
return affected;
}
private static int drawPrism(CommandContext<ServerCommandSource> ctx, String base){
BlockPos pos;
double radius;
int height;
String orientation;
BlockStateArgument block;
Predicate<CachedBlockPosition> replacement;
try
{
pos = getArg(ctx, BlockPosArgumentType::getBlockPos, "center");
radius = getArg(ctx, IntegerArgumentType::getInteger, "radius")+0.5D;
height = getArg(ctx, IntegerArgumentType::getInteger, "height");
orientation = getArg(ctx, StringArgumentType::getString,"orientation");
block = getArg(ctx, BlockStateArgumentType::getBlockState, "block");
replacement = getArg(ctx, BlockPredicateArgumentType::getBlockPredicate, "filter", true);
}
catch (ErrorHandled | CommandSyntaxException ignored) { return 0; }
ServerCommandSource source = ctx.getSource();
int affected = 0;
BlockPos.Mutable mbpos = new BlockPos.Mutable(pos);
List<BlockPos> list = Lists.newArrayList();
ServerWorld world = source.getWorld();
CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates;
boolean isSquare = base.equalsIgnoreCase("square");
for(int i =0; i<height;++i)
{
affected+= fillFlat(world, pos, i, radius, isSquare, orientation, block, replacement, list, mbpos);
}
CarpetSettings.impendingFillSkipUpdates = false;
if (CarpetSettings.fillUpdates) {
for (BlockPos blockpos1 : list) {
Block blokc = world.getBlockState(blockpos1).getBlock();
world.updateNeighbors(blockpos1, blokc);
}
}
Messenger.m(source, "gi Filled " + affected + " blocks");
return affected;
}
}
@@ -296,6 +296,14 @@ public class PlayerCommand
Messenger.m(context.getSource(), "r Cannot shadow fake players");
return 0;
}
ServerPlayerEntity sendingPlayer = null;
try
{
sendingPlayer = context.getSource().getPlayer();
}
catch (CommandSyntaxException ignored) { }
if (sendingPlayer!=player && cantManipulate(context)) return 0;
EntityPlayerMPFake.createShadow(player.server, player);
return 1;
}
@@ -16,6 +16,7 @@ import net.minecraft.command.arguments.DimensionArgumentType;
import net.minecraft.entity.EntityCategory;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.DyeColor;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.dimension.DimensionType;
@@ -98,7 +99,8 @@ public class SpawnCommand
executes( (c) -> generalMobcaps(c.getSource()) ).
then(argument("type", string()).
suggests( (c, b)->suggestMatching(Arrays.stream(EntityCategory.values()).map(EntityCategory::getName), b)).
executes( (c) -> listEntitiesOfType(c.getSource(), getString(c, "type")))));
executes( (c) -> listEntitiesOfType(c.getSource(), getString(c, "type"), false)).
then(literal("all").executes( (c) -> listEntitiesOfType(c.getSource(), getString(c, "type"), true)))));
dispatcher.register(literalargumentbuilder);
}
@@ -194,7 +196,7 @@ public class SpawnCommand
TickSpeed.tickrate_advance(null, 0, null, null);
// tick warp given player
ServerCommandSource csource = null;
PlayerEntity player = null;
ServerPlayerEntity player = null;
try
{
player = source.getPlayer();
@@ -262,10 +264,10 @@ public class SpawnCommand
return 1;
}
private static int listEntitiesOfType(ServerCommandSource source, String mobtype) throws CommandSyntaxException
private static int listEntitiesOfType(ServerCommandSource source, String mobtype, boolean all) throws CommandSyntaxException
{
EntityCategory cat = getCategory(mobtype);
Messenger.send(source, SpawnReporter.printEntitiesByType(cat, source.getWorld()));
Messenger.send(source, SpawnReporter.printEntitiesByType(cat, source.getWorld(), all));
return 1;
}
}
+10 -5
View File
@@ -9,6 +9,7 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.BaseText;
import net.minecraft.server.command.ServerCommandSource;
@@ -43,7 +44,8 @@ public class TickCommand
c.getSource(),
getInteger(c,"ticks"),
getString(c, "tail command")))))).
then(literal("freeze").executes( (c)-> toggleFreeze(c.getSource()))).
then(literal("freeze").executes( (c)-> toggleFreeze(c.getSource(), false)).
then(literal("deep").executes( (c)-> toggleFreeze(c.getSource(), true)))).
then(literal("step").
executes((c) -> step(1)).
then(argument("ticks", integer(1,72000)).
@@ -66,7 +68,7 @@ public class TickCommand
private static int setTps(ServerCommandSource source, float tps)
{
TickSpeed.tickrate(tps);
TickSpeed.tickrate(tps, true);
queryTps(source);
return (int)tps;
}
@@ -79,7 +81,7 @@ public class TickCommand
private static int setWarp(ServerCommandSource source, int advance, String tail_command)
{
PlayerEntity player = null;
ServerPlayerEntity player = null;
try
{
player = source.getPlayer();
@@ -95,15 +97,18 @@ public class TickCommand
return 1;
}
private static int toggleFreeze(ServerCommandSource source)
private static int toggleFreeze(ServerCommandSource source, boolean isDeep)
{
TickSpeed.is_paused = !TickSpeed.is_paused;
if (TickSpeed.is_paused)
{
Messenger.m(source, "gi Game is paused");
TickSpeed.deepFreeze = isDeep;
Messenger.m(source, "gi Game is "+(isDeep?"deeply ":"")+"frozen");
}
else
{
TickSpeed.deepFreeze = false;
Messenger.m(source, "gi Game runs normally");
}
return 1;
@@ -0,0 +1,12 @@
package carpet.fakes;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.util.SortedArraySet;
import net.minecraft.util.math.ChunkPos;
public interface ChunkTicketManagerInterface
{
void changeSpawnChunks(ChunkPos pos, int distance);
Long2ObjectOpenHashMap<SortedArraySet<ChunkTicket<?>>> getTicketsByPosition();
}
@@ -0,0 +1,10 @@
package carpet.fakes;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
public interface CustomPayloadC2SPacketInterface
{
Identifier getPacketChannel();
PacketByteBuf getPacketData();
}
@@ -11,4 +11,8 @@ public interface EntityInterface
boolean isPermanentVehicle();
void setPermanentVehicle(boolean permanent);
int getPortalTimer();
void setPortalTimer(int amount);
}
@@ -0,0 +1,6 @@
package carpet.fakes;
public interface HungerManagerInterface {
float getExhaustionCM();
void setExhaustionCM(float exhaust);
}
@@ -2,6 +2,6 @@ package carpet.fakes;
public interface ItemEntityInterface
{
int getAge();
int getPickupDelay();
int getAgeCM();
int getPickupDelayCM();
}
@@ -0,0 +1,7 @@
package carpet.fakes;
public interface LivingEntityInterface
{
void doJumpCM(); // added CM suffix to remove potential collisions with other mods
boolean isJumpingCM();
}
@@ -0,0 +1,8 @@
package carpet.fakes;
import net.minecraft.server.world.ChunkTicketManager;
public interface ServerChunkManagerInterface
{
ChunkTicketManager getCMTicketManager();
}
@@ -263,6 +263,15 @@ public class BlockRotator
}
world.setBlockState(blockpos, iblockstate.with(HorizontalFacingBlock.FACING, face), 3);
}
else if(block == Blocks.HOPPER )
{
Direction face = iblockstate.get(HopperBlock.FACING);
if (face != Direction.DOWN)
{
face = rotateClockwise(face, Direction.Axis.Y);
world.setBlockState(blockpos, iblockstate.with(HopperBlock.FACING, face), 3);
}
}
// Send block update to the block that just have been rotated.
world.updateNeighbor(blockpos, block, source.getBlockPos());
@@ -270,8 +279,6 @@ public class BlockRotator
}
public static boolean flip_block(BlockState state, World world, PlayerEntity player, Hand hand, BlockHitResult hit)
{
Block block = state.getBlock();
@@ -390,6 +390,7 @@ public class EntityPlayerActionPack
{
player.interactionManager.processBlockBreakingAction(pos, PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK, side, player.server.getWorldHeight());
ap.currentBlock = null;
ap.blockHitDelay = 5;
blockBroken = true;
}
player.world.setBlockBreakingInfo(-1, pos, (int) (ap.curBlockDamageMP * 10));
@@ -6,13 +6,19 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import net.minecraft.block.Blocks;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.structure.StructureManager;
import net.minecraft.structure.StructureStart;
import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import net.minecraft.world.biome.DefaultBiomeFeatures;
import net.minecraft.world.biome.source.BiomeAccess;
import net.minecraft.world.biome.source.BiomeSource;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSourceConfig;
import net.minecraft.world.gen.ChunkRandom;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.world.gen.chunk.OverworldChunkGenerator;
import net.minecraft.world.gen.chunk.OverworldChunkGeneratorConfig;
@@ -141,26 +147,47 @@ public class FeatureGenerator
return null;
}
public static StructureStart shouldStructureStartAt(ServerWorld world, BlockPos pos, StructureFeature<?> structure, boolean computeBox)
{
ChunkGenerator<?> generator = world.getChunkManager().getChunkGenerator();
if (!generator.getBiomeSource().hasStructureFeature(structure))
return null;
BiomeAccess biomeAccess = world.getBiomeAccess().withSource(generator.getBiomeSource());
ChunkRandom chunkRandom = new ChunkRandom();
ChunkPos chunkPos = new ChunkPos(pos);
Biome biome = biomeAccess.getBiome(new BlockPos(chunkPos.getStartX() + 9, 0, chunkPos.getStartZ() + 9));
if (structure.shouldStartAt(biomeAccess, generator, chunkRandom, chunkPos.x, chunkPos.z, biome))
{
if (!computeBox) return StructureStart.DEFAULT;
StructureManager manager = world.getSaveHandler().getStructureManager();
StructureStart structureStart3 = structure.getStructureStartFactory().create(structure, chunkPos.x, chunkPos.z, BlockBox.empty(), 0, generator.getSeed());
structureStart3.initialize(generator, manager, chunkPos.x, chunkPos.z, biome);
if (!structureStart3.hasChildren()) return null;
return structureStart3;
}
return null;
}
public static final Map<String, List<String>> structureToFeature = new HashMap<>();
public static final Map<String, String> featureToStructure = new HashMap<>();
static
{
structureToFeature.put("Monument", Collections.singletonList("monument"));
structureToFeature.put("EndCity", Collections.singletonList("end_city"));
structureToFeature.put("Ocean_Ruin", Arrays.asList("ocean_ruin", "ocean_ruin_warm", "ocean_ruin_small", "ocean_ruin_warm_small", "ocean_ruin_tall", "ocean_ruin_warm_tall"));
structureToFeature.put("Village", Arrays.asList("village", "village_desert", "village_savanna", "village_taiga", "village_snowy"));
structureToFeature.put("Mansion", Collections.singletonList("mansion"));
structureToFeature.put("Buried_Treasure", Collections.singletonList("treasure"));
structureToFeature.put("Swamp_Hut", Collections.singletonList("witch_hut"));
structureToFeature.put("Stronghold", Collections.singletonList("stronghold"));
structureToFeature.put("Desert_Pyramid", Collections.singletonList("desert_temple"));
structureToFeature.put("Jungle_Pyramid", Collections.singletonList("jungle_temple"));
structureToFeature.put("Shipwreck", Arrays.asList("shipwreck", "shipwreck2"));
structureToFeature.put("Pillager_Outpost", Collections.singletonList("pillager_outpost"));
structureToFeature.put("Mineshaft", Arrays.asList("mineshaft", "mineshaft_mesa"));
structureToFeature.put("Igloo", Collections.singletonList("igloo"));
structureToFeature.put("Fortress", Collections.singletonList("fortress"));
structureToFeature.put(Feature.OCEAN_MONUMENT.getName(), Collections.singletonList("monument"));
structureToFeature.put(Feature.END_CITY.getName(), Collections.singletonList("end_city"));
structureToFeature.put(Feature.OCEAN_RUIN.getName(), Arrays.asList("ocean_ruin", "ocean_ruin_warm", "ocean_ruin_small", "ocean_ruin_warm_small", "ocean_ruin_tall", "ocean_ruin_warm_tall"));
structureToFeature.put(Feature.VILLAGE.getName(), Arrays.asList("village", "village_desert", "village_savanna", "village_taiga", "village_snowy"));
structureToFeature.put(Feature.WOODLAND_MANSION.getName(), Collections.singletonList("mansion"));
structureToFeature.put(Feature.BURIED_TREASURE.getName(), Collections.singletonList("treasure"));
structureToFeature.put(Feature.SWAMP_HUT.getName(), Collections.singletonList("witch_hut"));
structureToFeature.put(Feature.STRONGHOLD.getName(), Collections.singletonList("stronghold"));
structureToFeature.put(Feature.DESERT_PYRAMID.getName(), Collections.singletonList("desert_temple"));
structureToFeature.put(Feature.JUNGLE_TEMPLE.getName(), Collections.singletonList("jungle_temple"));
structureToFeature.put(Feature.SHIPWRECK.getName(), Arrays.asList("shipwreck", "shipwreck2"));
structureToFeature.put(Feature.PILLAGER_OUTPOST.getName(), Collections.singletonList("pillager_outpost"));
structureToFeature.put(Feature.MINESHAFT.getName(), Arrays.asList("mineshaft", "mineshaft_mesa"));
structureToFeature.put(Feature.IGLOO.getName(), Collections.singletonList("igloo"));
structureToFeature.put(Feature.NETHER_BRIDGE.getName(), Collections.singletonList("fortress"));
structureToFeature.forEach((key, value) -> value.forEach(el -> featureToStructure.put(el, key)));
}
@@ -126,7 +126,7 @@ public class HopperCounter
color, ticks*1.0/(20*60), (realTime?" - real time":""), total, total*1.0*(20*60*60)/ticks),
"nb [X]", "^g reset", "!/counter "+color+" reset"
));
items.addAll(counter.object2LongEntrySet().stream().map(e ->
items.addAll(counter.object2LongEntrySet().stream().sorted((e, f) -> Long.compare(f.getLongValue(), e.getLongValue())).map(e ->
{
BaseText itemName = new TranslatableText(e.getKey().getTranslationKey());
long count = e.getLongValue();
@@ -7,8 +7,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import carpet.logging.logHelpers.ExplosionLogHelper;
import carpet.mixins.ExplosionAccessor;
import carpet.CarpetSettings;
import carpet.mixins.ExplosionMixin;
import carpet.utils.Messenger;
import com.google.common.collect.Lists;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
@@ -51,6 +53,7 @@ public class OptimizedExplosion
private static List<Entity> entitylist;
private static Vec3d vec3dmem;
private static long tickmem;
private static Vec3d vec3dZero = new Vec3d(0, 0, 0);
// For disabling the explosion particles and sound
public static int explosionSound = 0;
@@ -66,7 +69,7 @@ public class OptimizedExplosion
private static ArrayList<Float> chances = new ArrayList<>();
private static BlockPos blastChanceLocation;
public static void doExplosionA(Explosion e) {
public static void doExplosionA(Explosion e, ExplosionLogHelper eLogger) {
ExplosionAccessor eAccess = (ExplosionAccessor) e;
blastCalc(e);
@@ -120,6 +123,9 @@ public class OptimizedExplosion
entity.getX() == eAccess.getEntity().getX() &&
entity.getY() == eAccess.getEntity().getY() &&
entity.getZ() == eAccess.getEntity().getZ()) {
if (eLogger != null) {
eLogger.onEntityImpacted(entity, new Vec3d(0,-0.9923437498509884d, 0));
}
continue;
}
@@ -158,6 +164,10 @@ public class OptimizedExplosion
d11 = ProtectionEnchantment.transformExplosionKnockback((LivingEntity) entity, d10);
}
if (eLogger != null) {
eLogger.onEntityImpacted(entity, new Vec3d(d5 * d11, d7 * d11, d9 * d11));
}
entity.setVelocity(entity.getVelocity().add(d5 * d11, d7 * d11, d9 * d11));
if (entity instanceof PlayerEntity) {
+30 -7
View File
@@ -5,11 +5,12 @@ import java.util.Map;
import java.util.function.BiConsumer;
import carpet.CarpetServer;
import carpet.network.ServerNetworkHandler;
import carpet.utils.Messenger;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.BaseText;
public class TickSpeed
@@ -20,11 +21,12 @@ public class TickSpeed
public static long time_bias = 0;
public static long time_warp_start_time = 0;
public static long time_warp_scheduled_ticks = 0;
public static PlayerEntity time_advancerer = null;
public static ServerPlayerEntity time_advancerer = null;
public static String tick_warp_callback = null;
public static ServerCommandSource tick_warp_sender = null;
public static int player_active_timeout = 0;
public static boolean process_entities = true;
public static boolean deepFreeze = false;
public static boolean is_paused = false;
public static boolean is_superHot = false;
@@ -43,18 +45,37 @@ public class TickSpeed
}
}
public static void reset()
{
tickrate = 20.0f;
mspt = 50.0f;
time_bias = 0;
time_warp_start_time = 0;
time_warp_scheduled_ticks = 0;
time_advancerer = null;
tick_warp_callback = null;
tick_warp_sender = null;
player_active_timeout = 0;
process_entities = true;
deepFreeze = false;
is_paused = false;
is_superHot = false;
notifyTickrateListeners("carpet");
}
public static void add_ticks_to_run_in_pause(int ticks)
{
player_active_timeout = PLAYER_GRACE+ticks;
}
public static BaseText tickrate_advance(PlayerEntity player, int advance, String callback, ServerCommandSource source)
public static BaseText tickrate_advance(ServerPlayerEntity player, int advance, String callback, ServerCommandSource source)
{
if (0 == advance)
{
tick_warp_callback = null;
tick_warp_sender = null;
if (source != tick_warp_sender) tick_warp_sender = null;
finish_time_warp();
tick_warp_sender = null;
return Messenger.c("gi Warp interrupted");
}
if (time_bias > 0)
@@ -157,8 +178,9 @@ public class TickSpeed
}
}
}
public static void tickrate(float rate)
//unused - mod compat reasons
public static void tickrate(float rate) {tickrate(rate, true);}
public static void tickrate(float rate, boolean update)
{
tickrate = rate;
long mspt = (long)(1000.0 / tickrate);
@@ -170,7 +192,7 @@ public class TickSpeed
TickSpeed.mspt = (float)mspt;
notifyTickrateListeners("carpet");
if (update) notifyTickrateListeners("carpet");
}
private static void tickrateChanged(String modId, float rate)
@@ -202,6 +224,7 @@ public class TickSpeed
}
}
}
ServerNetworkHandler.updateTickSpeedToConnectedPlayers();
}
public static BiConsumer<String, Float> addTickrateListener(String modId, BiConsumer<String, Float> tickrateListener)
@@ -30,6 +30,7 @@ public class LoggerRegistry
public static boolean __packets;
public static boolean __weather;
public static boolean __pathfinding;
public static boolean __explosions;
public static void initLoggers()
{
@@ -52,6 +53,7 @@ public class LoggerRegistry
registerLogger("packets", HUDLogger.stardardHUDLogger("packets", null, null));
registerLogger("counter",HUDLogger.stardardHUDLogger("counter","white", Arrays.stream(DyeColor.values()).map(Object::toString).toArray(String[]::new)));
registerLogger("mobcaps", HUDLogger.stardardHUDLogger("mobcaps", "dynamic",new String[]{"dynamic", "overworld", "nether","end"}));
registerLogger("explosions", HUDLogger.stardardLogger("explosions", "brief",new String[]{"brief", "full"}));
}
@@ -0,0 +1,125 @@
package carpet.logging.logHelpers;
import carpet.logging.LoggerRegistry;
import carpet.utils.Messenger;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.text.BaseText;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.explosion.Explosion;
import java.util.ArrayList;
import java.util.List;
import static carpet.utils.Messenger.c;
public class ExplosionLogHelper
{
private final boolean createFire;
private final Explosion.DestructionType blockDestructionType;
public final Vec3d pos;
public final Entity entity;
private final float power;
private boolean affectBlocks = false;
private Object2IntMap<EntityChangedStatusWithCount> impactedEntities = new Object2IntOpenHashMap<>();
private static long lastGametime = 0;
private static int explosionCountInCurretGT = 0;
public ExplosionLogHelper(Entity entity, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType blockDestructionType) {
this.entity = entity;
this.power = power;
this.pos = new Vec3d(x,y,z);
this.createFire = createFire;
this.blockDestructionType = blockDestructionType;
}
public void setAffectBlocks(boolean b)
{
affectBlocks = b;
}
public void onExplosionDone(long gametime)
{
List<BaseText> messages = new ArrayList<>();
if (!(lastGametime == gametime)){
explosionCountInCurretGT = 0;
lastGametime = gametime;
messages.add(c("wb tick : ", "d " + gametime));
}
explosionCountInCurretGT++;
LoggerRegistry.getLogger("explosions").log( (option) -> {
if ("brief".equals(option))
{
messages.add( c("d #" + explosionCountInCurretGT,"gb ->",
Messenger.dblt("l", pos.x, pos.y, pos.z), (affectBlocks)?"m (affects blocks)":"m (doesn't affect blocks)" ));
}
if ("full".equals(option))
{
messages.add( c("d #" + explosionCountInCurretGT,"gb ->", Messenger.dblt("l", pos.x, pos.y, pos.z) ));
messages.add(c("w affects blocks: ", "m " + this.affectBlocks));
messages.add(c("w creates fire: ", "m " + this.createFire));
messages.add(c("w power: ", "c " + this.power));
messages.add(c( "w destruction: ", "c " + this.blockDestructionType.name()));
if (impactedEntities.isEmpty())
{
messages.add(c("w affected entities: ", "m None"));
}
else
{
messages.add(c("w affected entities:"));
impactedEntities.forEach((k, v) ->
{
messages.add(c((k.pos.equals(pos))?"r - TNT":"w - ",
Messenger.dblt((k.pos.equals(pos))?"r":"y", k.pos.x, k.pos.y, k.pos.z), "w dV",
Messenger.dblt("d", k.accel.x, k.accel.y, k.accel.z),
"w "+Registry.ENTITY_TYPE.getId(k.type).getPath(), (v>1)?"l ("+v+")":""
));
});
}
}
return messages.toArray(new BaseText[0]);
});
}
public void onEntityImpacted(Entity entity, Vec3d accel)
{
EntityChangedStatusWithCount ent = new EntityChangedStatusWithCount(entity, accel);
impactedEntities.put(ent, impactedEntities.getOrDefault(ent, 0)+1);
}
public static class EntityChangedStatusWithCount
{
public final Vec3d pos;
public final EntityType type;
public final Vec3d accel;
public EntityChangedStatusWithCount(Entity e, Vec3d accel)
{
this.pos = e.getPos();
this.type = e.getType();
this.accel = accel;
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof EntityChangedStatusWithCount)
{
EntityChangedStatusWithCount other = (EntityChangedStatusWithCount) obj;
return other.pos.equals(pos) && other.accel.equals(accel) && other.type.equals(type);
}
return super.equals(obj);
}
@Override
public int hashCode()
{
return pos.hashCode()+ type.hashCode()+accel.hashCode();
}
}
}
@@ -9,6 +9,8 @@ public class TNTLogHelper
{
public boolean initialized;
private double primedX, primedY, primedZ;
private static long lastGametime = 0;
private static int tntCount = 0;
private Vec3d primedAngle;
/**
* Runs when the TNT is primed. Expects the position and motion angle of the TNT.
@@ -24,8 +26,13 @@ public class TNTLogHelper
/**
* Runs when the TNT explodes. Expects the position of the TNT.
*/
public void onExploded(double x, double y, double z)
public void onExploded(double x, double y, double z, long gametime)
{
if (!(lastGametime == gametime)){
tntCount = 0;
lastGametime = gametime;
}
tntCount++;
LoggerRegistry.getLogger("tnt").log( (option) -> {
switch (option)
{
@@ -36,6 +43,9 @@ public class TNTLogHelper
"r E ",Messenger.dblt("r",x, y, z))};
case "full":
return new BaseText[]{Messenger.c(
"r #" + String.valueOf(tntCount),
"m @" + String.valueOf(gametime),
"g : ",
"l P ",Messenger.dblf("l",primedX,primedY,primedZ),
"w ",Messenger.dblf("l", primedAngle.x, primedAngle.y, primedAngle.z),
"r E ",Messenger.dblf("r",x, y, z))};
@@ -0,0 +1,36 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.block.BlockState;
import net.minecraft.entity.EntityContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemPlacementContext;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(BlockItem.class)
public class BlockItem_creativeNoClipMixin
{
@Redirect(method = "canPlace", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/World;canPlace(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/entity/EntityContext;)Z"
))
private boolean canSpectatingPlace(World world, BlockState state, BlockPos pos, EntityContext context,
ItemPlacementContext contextOuter, BlockState stateOuter)
{
PlayerEntity player = contextOuter.getPlayer();
if (CarpetSettings.creativeNoClip && player != null && player.isCreative() && player.abilities.flying)
{
// copy from canPlace
VoxelShape voxelShape = state.getCollisionShape(world, pos, context);
return voxelShape.isEmpty() || world.intersectsEntities(player, voxelShape.offset(pos.getX(), pos.getY(), pos.getZ()));
}
return world.canPlace(state, pos, context);
}
}
@@ -11,6 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Chunk.class)
public abstract class ChunkMixin
{
// failed to mixin into interface. need to mixin in two places that uses it
@Inject(method = "markBlockForPostProcessing(Lnet/minecraft/util/math/BlockPos;)V",
at =@At("HEAD"), cancellable = true)
private void squashWarnings(BlockPos blockPos_1, CallbackInfo ci)
@@ -0,0 +1,19 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ChunkRegion;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ChunkRegion.class)
public class ChunkRegion_scarpetPlopMixin
{
@Inject(method = "markBlockForPostProcessing", at = @At("HEAD"))
private void markOrNot(BlockPos blockPos, CallbackInfo ci)
{
if (CarpetSettings.skipGenerationChecks) ci.cancel();
}
}
@@ -1,15 +1,23 @@
package carpet.mixins;
import carpet.fakes.ChunkTicketManagerInterface;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.util.SortedArraySet;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(ChunkTicketManager.class)
public interface ChunkTicketManager_scarpetMixin
public abstract class ChunkTicketManager_scarpetMixin implements ChunkTicketManagerInterface
{
@Accessor
Long2ObjectOpenHashMap<SortedArraySet<ChunkTicket<?>>> getTicketsByPosition();
@Shadow @Final private Long2ObjectOpenHashMap<SortedArraySet<ChunkTicket<?>>> ticketsByPosition;
@Override
public Long2ObjectOpenHashMap<SortedArraySet<ChunkTicket<?>>> getTicketsByPosition()
{
return ticketsByPosition;
}
}
@@ -0,0 +1,60 @@
package carpet.mixins;
import carpet.fakes.ChunkTicketManagerInterface;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.world.ChunkTicket;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.server.world.ChunkTicketType;
import net.minecraft.util.SortedArraySet;
import net.minecraft.util.Unit;
import net.minecraft.util.math.ChunkPos;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Iterator;
@Mixin(ChunkTicketManager.class)
public abstract class ChunkTicketManager_spawnChunksMixin implements ChunkTicketManagerInterface
{
@Shadow protected abstract SortedArraySet<ChunkTicket<?>> getTicketSet(long position);
@Shadow @Final private Long2ObjectOpenHashMap<SortedArraySet<ChunkTicket<?>>> ticketsByPosition;
@Shadow public abstract <T> void removeTicket(ChunkTicketType<T> type, ChunkPos pos, int radius, T argument);
@Shadow protected abstract void removeTicket(long pos, ChunkTicket<?> ticket);
@Shadow public abstract <T> void addTicket(ChunkTicketType<T> type, ChunkPos pos, int radius, T argument);
@Override
public void changeSpawnChunks(ChunkPos chunkPos, int distance)
{
long pos = chunkPos.toLong();
SortedArraySet<ChunkTicket<?>> set = ticketsByPosition.get(pos);
ChunkTicket existingTicket = null;
if (set != null)
{
Iterator<ChunkTicket<?>> iter = set.iterator();
while(iter.hasNext())
{
ChunkTicket ticket = iter.next();
if (ticket.getType() == ChunkTicketType.START)
{
existingTicket = ticket;
iter.remove();
}
}
set.add(existingTicket);
}
// the reason we are removing the ticket this way is that there are sideeffects of removal
if (existingTicket != null)
{
removeTicket(pos, existingTicket);
}
// set optionally new spawn ticket
if (distance > 0)
addTicket(ChunkTicketType.START, chunkPos, distance, Unit.INSTANCE);
}
}
@@ -0,0 +1,23 @@
package carpet.mixins;
import carpet.helpers.TickSpeed;
import net.minecraft.server.world.ChunkTicketManager;
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;
@Mixin(ChunkTicketManager.class)
public class ChunkTicketManager_tickMixin
{
@Shadow private long age;
@Inject(method = "purge", at = @At("HEAD"), cancellable = true)
private void pauseTicketSystem(CallbackInfo ci)
{
// pausing expiry of tickets
// that will prevent also chunks from unloading, so require a deep frozen state
if (!TickSpeed.process_entities && TickSpeed.deepFreeze) age--;
}
}
@@ -0,0 +1,43 @@
package carpet.mixins;
import carpet.network.CarpetClient;
import carpet.network.ClientNetworkHandler;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket;
import net.minecraft.text.Text;
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;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class ClientPlayNetworkHandlerMixin
{
@Shadow private MinecraftClient client;
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
private void onOnCustomPayload(CustomPayloadS2CPacket packet, CallbackInfo ci)
{
if (CarpetClient.CARPET_CHANNEL.equals(packet.getChannel()))
{
ClientNetworkHandler.handleData(packet.getData(), client.player);
ci.cancel();
}
}
@Inject(method = "onGameJoin", at = @At("RETURN"))
private void onGameJoined(GameJoinS2CPacket packet, CallbackInfo info)
{
CarpetClient.gameJoined(client.player);
}
@Inject(method = "onDisconnected", at = @At("HEAD"))
private void onCMDisconnected(Text reason, CallbackInfo ci)
{
CarpetClient.disconnect();
}
}
@@ -1,6 +1,7 @@
package carpet.mixins;
import carpet.CarpetServer;
import carpet.network.CarpetClient;
import net.minecraft.client.network.ClientPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -13,7 +14,10 @@ public class ClientPlayerEntity_clientCommandMixin
@Inject(method = "sendChatMessage", at = @At("HEAD"))
private void inspectMessage(String string, CallbackInfo ci)
{
if (CarpetServer.minecraft_server == null)
CarpetServer.settingsManager.inspectClientsideCommand(string);
if (CarpetServer.minecraft_server == null && !CarpetClient.isCarpet())
{
ClientPlayerEntity playerSource = (ClientPlayerEntity)(Object) this;
CarpetServer.settingsManager.inspectClientsideCommand(playerSource.getCommandSource(), string);
}
}
}
@@ -0,0 +1,27 @@
package carpet.mixins;
import carpet.fakes.CustomPayloadC2SPacketInterface;
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.util.Identifier;
import net.minecraft.util.PacketByteBuf;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(CustomPayloadC2SPacket.class)
public class CustomPayloadC2SPacketMixin implements CustomPayloadC2SPacketInterface
{
@Shadow
private Identifier channel;
@Shadow
private PacketByteBuf data;
@Override
public Identifier getPacketChannel() {
return channel;
}
@Override
public PacketByteBuf getPacketData() {
return new PacketByteBuf(this.data.copy());
}
}
@@ -5,6 +5,7 @@ import carpet.script.EntityEventsGroup;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.damage.DamageTracker;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -12,8 +13,11 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static carpet.script.CarpetEventServer.Event.PLAYER_DEALS_DAMAGE;
import static carpet.script.CarpetEventServer.Event.PLAYER_TAKES_DAMAGE;
@Mixin(DamageTracker.class)
public class DamageTracker_scarpetEventMixin
public abstract class DamageTracker_scarpetEventMixin
{
@Shadow @Final private LivingEntity entity;
@@ -21,5 +25,13 @@ public class DamageTracker_scarpetEventMixin
private void onDamageTaken(DamageSource damageSource_1, float float_1, float float_2, CallbackInfo ci)
{
((EntityInterface)entity).getEventContainer().onEvent(EntityEventsGroup.EntityEventType.ON_DAMAGE, entity, float_2, damageSource_1);
if (entity instanceof ServerPlayerEntity && PLAYER_TAKES_DAMAGE.isNeeded())
{
PLAYER_TAKES_DAMAGE.onDamage(entity, float_2, damageSource_1);
}
if (damageSource_1.getAttacker() instanceof ServerPlayerEntity && PLAYER_DEALS_DAMAGE.isNeeded())
{
PLAYER_DEALS_DAMAGE.onDamage(entity, float_2, damageSource_1);
}
}
}
@@ -0,0 +1,19 @@
package carpet.mixins;
import carpet.network.CarpetClient;
import net.minecraft.client.render.debug.DebugRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(DebugRenderer.class)
public class DebugRenderer_scarpetRenderMixin
{
@Inject(method = "reset", at = @At("HEAD"))
private void resetScarpetRenderes(CallbackInfo ci)
{
if (CarpetClient.shapes != null)
CarpetClient.shapes.reset();
}
}
@@ -14,6 +14,7 @@ public abstract class Entity_scarpetEventsMixin implements EntityInterface
{
@Shadow public boolean removed;
@Shadow protected int netherPortalTime;
private boolean permanentVehicle;
private final EntityEventsGroup events = new EntityEventsGroup();
@@ -36,6 +37,18 @@ public abstract class Entity_scarpetEventsMixin implements EntityInterface
permanentVehicle = permanent;
}
@Override
public int getPortalTimer()
{
return netherPortalTime;
}
@Override
public void setPortalTimer(int amount)
{
netherPortalTime = amount;
}
@Inject(method = "tick", at = @At("HEAD"))
private void onTickCall(CallbackInfo ci)
{
@@ -2,9 +2,13 @@ package carpet.mixins;
import carpet.helpers.OptimizedExplosion;
import carpet.CarpetSettings;
import carpet.logging.LoggerRegistry;
import carpet.logging.logHelpers.ExplosionLogHelper;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.explosion.Explosion;
import org.spongepowered.asm.mixin.Final;
@@ -24,13 +28,17 @@ public abstract class ExplosionMixin
@Final
private List<BlockPos> affectedBlocks;
@Shadow @Final private World world;
private ExplosionLogHelper eLogger;
@Inject(method = "collectBlocksAndDamageEntities", at = @At("HEAD"),
cancellable = true)
private void onExplosionA(CallbackInfo ci)
{
if (CarpetSettings.optimizedTNT)
{
OptimizedExplosion.doExplosionA((Explosion) (Object) this);
OptimizedExplosion.doExplosionA((Explosion) (Object) this, eLogger);
ci.cancel();
}
}
@@ -39,6 +47,11 @@ public abstract class ExplosionMixin
cancellable = true)
private void onExplosionB(boolean spawnParticles, CallbackInfo ci)
{
if (eLogger != null)
{
eLogger.setAffectBlocks( ! affectedBlocks.isEmpty());
eLogger.onExplosionDone(this.world.getTime());
}
if (CarpetSettings.explosionNoBlockDamage)
{
affectedBlocks.clear();
@@ -58,4 +71,24 @@ public abstract class ExplosionMixin
if (CarpetSettings.explosionNoBlockDamage) return Blocks.BEDROCK.getDefaultState();
return world.getBlockState(pos);
}
@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/entity/Entity;DDDFZLnet/minecraft/world/explosion/Explosion$DestructionType;)V",
at = @At(value = "RETURN"))
private void onExplosionCreated(World world, Entity entity, double x, double y, double z, float power, boolean createFire, Explosion.DestructionType blockDestructionType, CallbackInfo ci)
{
if (LoggerRegistry.__explosions && ! world.isClient)
{
eLogger = new ExplosionLogHelper(entity, x, y, z, power, createFire, blockDestructionType);
}
}
@Redirect(method = "collectBlocksAndDamageEntities",
at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;setVelocity(Lnet/minecraft/util/math/Vec3d;)V"))
private void setVelocityAndUpdateLogging(Entity entity, Vec3d velocity)
{
if (eLogger != null) {
eLogger.onEntityImpacted(entity, velocity.subtract(entity.getVelocity()));
}
entity.setVelocity(velocity);
}
}
@@ -0,0 +1,20 @@
package carpet.mixins;
import carpet.fakes.HungerManagerInterface;
import net.minecraft.entity.player.HungerManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(HungerManager.class)
public class HungerManagerMixin_scarpetEventsMixin implements HungerManagerInterface {
@Shadow private float exhaustion;
public float getExhaustionCM(){
return exhaustion;
}
@Override
public void setExhaustionCM(float exhaust)
{
exhaustion = exhaust;
}
}
@@ -30,12 +30,12 @@ public abstract class ItemEntityMixin extends Entity implements ItemEntityInterf
}
@Override
public int getAge() {
public int getAgeCM() {
return this.age;
}
@Override
public int getPickupDelay() {
public int getPickupDelayCM() {
return this.pickupDelay;
}
@@ -91,8 +91,8 @@ public abstract class ItemEntityMixin extends Entity implements ItemEntityInterf
selfStack.increment(amount);
self.setStack(selfStack);
this.pickupDelay = Math.max(((ItemEntityInterface)other).getPickupDelay(), this.pickupDelay);
this.age = Math.min(((ItemEntityInterface)other).getAge(), this.age);
this.pickupDelay = Math.max(((ItemEntityInterface)other).getPickupDelayCM(), this.pickupDelay);
this.age = Math.min(((ItemEntityInterface)other).getAgeCM(), this.age);
otherStack.decrement(amount);
if (otherStack.isEmpty())
@@ -0,0 +1,50 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
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.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class LivingEntity_creativeFlyMixin extends Entity
{
@Shadow public float flyingSpeed;
public LivingEntity_creativeFlyMixin(EntityType<?> type, World world)
{
super(type, world);
}
@ModifyConstant(method = "travel", constant = @Constant(floatValue = 0.91F), expect = 2)
private float drag(float original)
{
if (CarpetSettings.creativeFlyDrag != 0.09 && (Object)this instanceof PlayerEntity)
{
PlayerEntity self = (PlayerEntity)(Object)(this);
if (self.abilities.flying && ! onGround )
return (float)(1.0-CarpetSettings.creativeFlyDrag);
}
return original;
}
@Inject(method = "getMovementSpeed(F)F", at = @At("HEAD"), cancellable = true)
private void flyingAltSpeed(float slipperiness, CallbackInfoReturnable<Float> cir)
{
if (CarpetSettings.creativeFlySpeed != 1.0D && (Object)this instanceof PlayerEntity)
{
PlayerEntity self = (PlayerEntity)(Object)(this);
if (self.abilities.flying && !onGround)
cir.setReturnValue(flyingSpeed* (float)CarpetSettings.creativeFlySpeed);
}
}
}
@@ -1,21 +1,33 @@
package carpet.mixins;
import carpet.CarpetSettings;
import carpet.fakes.EntityInterface;
import carpet.fakes.LivingEntityInterface;
import carpet.script.EntityEventsGroup;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
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.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class LivingEntity_scarpetEventsMixin extends Entity
public abstract class LivingEntity_scarpetEventsMixin extends Entity implements LivingEntityInterface
{
@Shadow protected abstract void jump();
@Shadow protected boolean jumping;
public LivingEntity_scarpetEventsMixin(EntityType<?> type, World world)
{
super(type, world);
@@ -26,4 +38,16 @@ public abstract class LivingEntity_scarpetEventsMixin extends Entity
{
((EntityInterface)this).getEventContainer().onEvent(EntityEventsGroup.EntityEventType.ON_DEATH, this, damageSource_1.name);
}
@Override
public void doJumpCM()
{
jump();
}
@Override
public boolean isJumpingCM()
{
return jumping;
}
}
@@ -1,7 +1,9 @@
package carpet.mixins;
import carpet.CarpetServer;
import carpet.network.CarpetClient;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -18,4 +20,10 @@ public class MinecraftClientMixin
//CM start game hook
CarpetServer.onGameStarted();
}
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At("HEAD"))
private void onCloseGame(Screen screen, CallbackInfo ci)
{
CarpetClient.disconnect();
}
}
@@ -1,8 +1,10 @@
package carpet.mixins;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import com.google.gson.JsonElement;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.WorldGenerationProgressListener;
import net.minecraft.world.level.LevelGeneratorType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -41,4 +43,11 @@ public abstract class MinecraftServer_coreMixin
{
CarpetServer.onServerClosed((MinecraftServer) (Object) this);
}
@Inject(method = "prepareStartRegion", at = @At("RETURN"))
private void afterSpawnCreated(WorldGenerationProgressListener worldGenerationProgressListener, CallbackInfo ci)
{
if (CarpetSettings.spawnChunksSize != 11)
CarpetSettings.ChangeSpawnChunksValidator.changeSpawnSize(CarpetSettings.spawnChunksSize);
}
}
@@ -120,7 +120,7 @@ public abstract class MinecraftServer_tickspeedMixin extends ReentrantThreadExec
this.profiler.startTick();
this.profiler.push("tick");
this.tick(TickSpeed.time_warp_start_time != 0 ? ()->true : this::shouldKeepTicking);
this.tick(TickSpeed.time_warp_start_time != 0 ? ()->!hasRunningTasks() : this::shouldKeepTicking);
this.profiler.swap("nextTickWait");
this.field_19249 = true;
this.field_19248 = Math.max(Util.getMeasuringTimeMs() + /*50L*/ msThisTick, this.timeReference);
@@ -0,0 +1,29 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.block.entity.PistonBlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.Direction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PistonBlockEntity.class)
public class PistonBlockEntity_creativeNoClipMixin
{
@Inject(method = "method_23672", at = @At("HEAD"), cancellable = true)
private static void dontPushSpectators(Direction direction, Entity entity, double d, Direction direction2, CallbackInfo ci)
{
if (CarpetSettings.creativeNoClip && entity instanceof PlayerEntity && (((PlayerEntity) entity).isCreative()) && ((PlayerEntity) entity).abilities.flying) ci.cancel();
}
@Redirect(method = "pushEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;setVelocity(DDD)V"))
private void ignoreAccel(Entity entity, double x, double y, double z)
{
if (CarpetSettings.creativeNoClip && entity instanceof PlayerEntity && (((PlayerEntity) entity).isCreative()) && ((PlayerEntity) entity).abilities.flying) return;
entity.setVelocity(x,y,z);
}
}
@@ -5,6 +5,7 @@ import carpet.fakes.PistonBlockEntityInterface;
import com.google.common.collect.Lists;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.piston.PistonBehavior;
import net.minecraft.block.piston.PistonHandler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
@@ -62,6 +63,17 @@ public abstract class PistonBlock_movableTEMixin extends FacingBlock
return !(CarpetSettings.movableBlockEntities && isPushableBlockEntity(block));
}
}
@Redirect(method = "isMovable", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/block/BlockState;getPistonBehavior()Lnet/minecraft/block/piston/PistonBehavior;"
))
private static PistonBehavior moveGrindstones(BlockState blockState)
{
if (CarpetSettings.movableBlockEntities && blockState.getBlock() == Blocks.GRINDSTONE) return PistonBehavior.NORMAL;
return blockState.getPistonBehavior();
}
@Inject(method = "move", at = @At(value = "INVOKE", shift = At.Shift.BEFORE,
target = "Ljava/util/List;size()I", ordinal = 4),locals = LocalCapture.CAPTURE_FAILHARD)
private void onMove(World world_1, BlockPos blockPos_1, Direction direction_1, boolean boolean_1,
@@ -23,7 +23,7 @@ public abstract class PlayerEntity_antiCheatDisabled
@Inject(method = "method_23668", at = @At("HEAD"), cancellable = true)
private void allowDeploys(CallbackInfoReturnable<Boolean> cir)
{
if (CarpetSettings.antiCheatDisabled && CarpetServer.minecraft_server.isDedicated())
if (CarpetSettings.antiCheatDisabled && CarpetServer.minecraft_server != null && CarpetServer.minecraft_server.isDedicated())
{
ItemStack itemStack_1 = getEquippedStack(EquipmentSlot.CHEST);
if (itemStack_1.getItem() == Items.ELYTRA && ElytraItem.isUsable(itemStack_1)) {
@@ -0,0 +1,56 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.MovementType;
import net.minecraft.entity.player.PlayerAbilities;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntity_creativeNoClipMixin extends LivingEntity
{
@Shadow public abstract boolean isCreative();
@Shadow @Final public PlayerAbilities abilities;
protected PlayerEntity_creativeNoClipMixin(EntityType<? extends LivingEntity> type, World world)
{
super(type, world);
}
@Redirect(method = "tick", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
)
private boolean canClipTroughWorld(PlayerEntity playerEntity)
{
return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
}
@Redirect(method = "tickMovement", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
)
private boolean collidesWithEntities(PlayerEntity playerEntity)
{
return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
}
@Redirect(method = "updateSize", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/player/PlayerEntity;isSpectator()Z")
)
private boolean spectatorsDontPose(PlayerEntity playerEntity)
{
return playerEntity.isSpectator() || (CarpetSettings.creativeNoClip && playerEntity.isCreative() && playerEntity.abilities.flying);
}
}
@@ -9,6 +9,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static carpet.script.CarpetEventServer.Event.PLAYER_CONNECTS;
@Mixin(PlayerManager.class)
public class PlayerManager_coreMixin
{
@@ -16,5 +18,6 @@ public class PlayerManager_coreMixin
private void onPlayerConnected(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci)
{
CarpetServer.onPlayerLoggedIn(player);
PLAYER_CONNECTS.onPlayerEvent(player);
}
}
@@ -0,0 +1,21 @@
package carpet.mixins;
import net.minecraft.server.PlayerManager;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.world.dimension.DimensionType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static carpet.script.CarpetEventServer.Event.PLAYER_RESPAWNS;
@Mixin(PlayerManager.class)
public class PlayerManager_scarpetEventsMixin
{
@Inject(method = "respawnPlayer", at = @At("HEAD"))
private void onRespawn(ServerPlayerEntity player, DimensionType dimension, boolean alive, CallbackInfoReturnable<ServerPlayerEntity> cir)
{
PLAYER_RESPAWNS.onPlayerEvent(player);
}
}
@@ -1,8 +1,10 @@
package carpet.mixins;
import carpet.fakes.ServerChunkManagerInterface;
import carpet.utils.SpawnReporter;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.minecraft.entity.EntityCategory;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.server.world.ServerChunkManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
@@ -24,12 +26,20 @@ import java.util.HashMap;
import java.util.HashSet;
@Mixin(ServerChunkManager.class)
public abstract class ServerChunkManagerMixin
public abstract class ServerChunkManagerMixin implements ServerChunkManagerInterface
{
@Shadow @Final private ServerWorld world;
@Shadow @Final private static int CHUNKS_ELIGIBLE_FOR_SPAWNING;
@Shadow @Final private ChunkTicketManager ticketManager;
@Override // shared between scarpet and spawnChunks setting
public ChunkTicketManager getCMTicketManager()
{
return ticketManager;
}
@Inject(
method = "tickChunks",
locals = LocalCapture.CAPTURE_FAILHARD,
@@ -1,13 +0,0 @@
package carpet.mixins;
import net.minecraft.server.world.ChunkTicketManager;
import net.minecraft.server.world.ServerChunkManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ServerChunkManager.class)
public interface ServerChunkManager_scarpetMixin
{
@Accessor
ChunkTicketManager getTicketManager();
}
@@ -0,0 +1,31 @@
package carpet.mixins;
import carpet.fakes.CustomPayloadC2SPacketInterface;
import carpet.network.CarpetClient;
import carpet.network.ServerNetworkHandler;
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
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;
@Mixin(ServerPlayNetworkHandler.class)
public class ServerPlayNetworkHandlerMixin
{
@Shadow public ServerPlayerEntity player;
@Inject(method = "onCustomPayload", at = @At("HEAD"), cancellable = true)
private void onCustomCarpetPayload(CustomPayloadC2SPacket packet, CallbackInfo ci)
{
Identifier channel = ((CustomPayloadC2SPacketInterface) packet).getPacketChannel();
if (CarpetClient.CARPET_CHANNEL.equals(channel))
{
ServerNetworkHandler.handleData(((CustomPayloadC2SPacketInterface) packet).getPacketData(), player);
ci.cancel();
}
}
}
@@ -13,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static carpet.script.CarpetEventServer.Event.PLAYER_DISCONNECTS;
@Mixin(ServerPlayNetworkHandler.class)
public class ServerPlayNetworkHandler_coreMixin {
@Shadow
@@ -21,5 +23,6 @@ public class ServerPlayNetworkHandler_coreMixin {
@Inject(method = "onDisconnected", at = @At("HEAD"))
private void onPlayerDisconnect(Text reason, CallbackInfo ci) {
CarpetServer.onPlayerLoggedOut(this.player);
if (PLAYER_DISCONNECTS.isNeeded()) PLAYER_DISCONNECTS.onPlayerMessage(player, reason.asString());
}
}
@@ -31,6 +31,10 @@ public class ServerPlayNetworkHandler_tickMixin
}
}
// to skip reposition adjustment check
private static long lastMovedTick = 0L;
private static double lastMoved = 0.0D;
@Inject(method = "onPlayerMove", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/network/ServerPlayerEntity;isSleeping()Z",
@@ -38,10 +42,23 @@ public class ServerPlayNetworkHandler_tickMixin
))
private void checkMove(PlayerMoveC2SPacket p, CallbackInfo ci)
{
if (Math.abs(p.getX(player.getX()) - lastTickX) > 0.0001D
|| Math.abs(p.getY(player.getY()) - lastTickY) > 0.0001D
|| Math.abs(p.getY(player.getZ()) - lastTickZ) > 0.0001D)
double movedBy = player.getPos().squaredDistanceTo(lastTickX, lastTickY, lastTickZ);
if (movedBy == 0.0D) return;
// corrective tick
if (movedBy < 0.0009 && lastMoved > 0.0009 && Math.abs(player.getServer().getTicks()-lastMovedTick-20)<2)
{
//CarpetSettings.LOG.error("Corrective movement packet");
return;
}
if (movedBy > 0.0D)
{
//CarpetSettings.LOG.error(String.format(
// "moved by %.6f at %d",
// player.getPos().squaredDistanceTo(lastTickX, lastTickY, lastTickZ),
// player.getServer().getTicks()-lastMovedTick
//));
lastMoved = movedBy;
lastMovedTick = player.getServer().getTicks();
TickSpeed.reset_player_active_timeout();
}
}
@@ -3,20 +3,26 @@ package carpet.mixins;
import carpet.fakes.EntityInterface;
import carpet.script.EntityEventsGroup;
import com.mojang.authlib.GameProfile;
import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.stat.Stat;
import net.minecraft.util.Hand;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static carpet.script.CarpetEventServer.Event.PLAYER_CHANGES_DIMENSION;
import static carpet.script.CarpetEventServer.Event.PLAYER_DIES;
import static carpet.script.CarpetEventServer.Event.PLAYER_FINISHED_USING_ITEM;
import static carpet.script.CarpetEventServer.Event.STATISTICS;
@@ -30,6 +36,8 @@ public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity
@Shadow protected abstract void consumeItem();
@Shadow public boolean notInAnyWorld;
@Redirect(method = "consumeItem", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/entity/player/PlayerEntity;consumeItem()V"
@@ -61,6 +69,10 @@ public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity
private void onDeathEvent(DamageSource source, CallbackInfo ci)
{
((EntityInterface)this).getEventContainer().onEvent(EntityEventsGroup.EntityEventType.ON_DEATH, this, source.name);
if (PLAYER_DIES.isNeeded())
{
PLAYER_DIES.onPlayerEvent((ServerPlayerEntity) (Object)this);
}
}
@Redirect(method = "method_14218", at = @At(
@@ -72,4 +84,30 @@ public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity
if (!((EntityInterface)serverPlayerEntity.getVehicle()).isPermanentVehicle()) // won't since that method makes sure its not null
serverPlayerEntity.setSneaking(sneaking);
}
private Vec3d previousLocation;
private DimensionType previousDimension;
@Inject(method = "changeDimension", at = @At("HEAD"))
private void logPreviousCoordinates(DimensionType newDimension, CallbackInfoReturnable<Entity> cir)
{
previousLocation = getPos();
previousDimension = dimension;
}
@Inject(method = "changeDimension", at = @At("RETURN"))
private void atChangeDimension(DimensionType newDimension, CallbackInfoReturnable<Entity> cir)
{
if (PLAYER_CHANGES_DIMENSION.isNeeded())
{
ServerPlayerEntity player = (ServerPlayerEntity) (Object)this;
Vec3d to = null;
if (!notInAnyWorld || previousDimension != DimensionType.THE_END || newDimension != DimensionType.OVERWORLD)
{
to = getPos();
}
PLAYER_CHANGES_DIMENSION.onDimensionChange(player, previousLocation, to, previousDimension, newDimension);
}
}
}
@@ -0,0 +1,17 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.server.world.ServerWorld;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(ServerWorld.class)
public class ServerWorld_spawnChunksMixin
{
@ModifyConstant(method = "setSpawnPos", constant = @Constant(intValue = 11), expect = 2)
private int pushLimit(int original)
{
return CarpetSettings.spawnChunksSize;
}
}
@@ -0,0 +1,22 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.structure.StructurePiece;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.chunk.Chunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(StructurePiece.class)
public class StructurePiece_scarpetPlopMixin
{
@Redirect(method = "addBlock", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/chunk/Chunk;markBlockForPostProcessing(Lnet/minecraft/util/math/BlockPos;)V"
))
private void markOrNot(Chunk chunk, BlockPos pos)
{
if (!CarpetSettings.skipGenerationChecks) chunk.markBlockForPostProcessing(pos);
}
}
@@ -72,7 +72,7 @@ public abstract class TntEntityMixin extends Entity implements TntEntityInterfac
private void onExplode(CallbackInfo ci)
{
if (LoggerRegistry.__tnt && logHelper != null)
logHelper.onExploded(getX(), getY(), getZ());
logHelper.onExploded(getX(), getY(), getZ(), this.world.getTime());
if (mergedTNT > 1)
for (int i = 0; i < mergedTNT - 1; i++)
@@ -0,0 +1,19 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.render.WorldRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(WorldRenderer.class)
public class WorldRenderer_creativeNoClipMixin
{
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isSpectator()Z"))
private boolean canSeeWorld(ClientPlayerEntity clientPlayerEntity)
{
return clientPlayerEntity.isSpectator() || (CarpetSettings.creativeNoClip && clientPlayerEntity.isCreative());
}
}
@@ -0,0 +1,23 @@
package carpet.mixins;
import carpet.CarpetSettings;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.world.dimension.Dimension;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(value = WorldRenderer.class, priority = 69420)
public class WorldRenderer_fogOffMixin
{
@Redirect(method = "render", require = 0, expect = 0, at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/dimension/Dimension;isFogThick(II)Z"
))
private boolean isReallyThick(Dimension dimension, int x, int z)
{
if (CarpetSettings.fogOff) return false;
return dimension.isFogThick(x, z);
}
}
@@ -0,0 +1,32 @@
package carpet.mixins;
import carpet.network.CarpetClient;
import carpet.script.utils.ShapesRenderer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilderStorage;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.render.LightmapTextureManager;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.util.math.Matrix4f;
import net.minecraft.client.util.math.MatrixStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class)
public class WorldRenderer_scarpetRenderMixin
{
@Inject(method = "<init>", at = @At("RETURN"))
private void addRenderers(MinecraftClient client, BufferBuilderStorage bufferBuilders, CallbackInfo ci)
{
CarpetClient.shapes = new ShapesRenderer(client);
}
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/debug/DebugRenderer;render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;DDD)V"))
private void renderScarpetThings(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci)
{
if (CarpetClient.shapes != null) CarpetClient.shapes.render(camera, tickDelta);
}
}
@@ -0,0 +1,54 @@
package carpet.network;
import carpet.CarpetServer;
import carpet.script.utils.ShapesRenderer;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.util.Identifier;
public class CarpetClient
{
public static final Object sync = new Object();
public static final int HI = 69;
public static final int HELLO = 420;
public static final int DATA = 1;
public static ShapesRenderer shapes = null;
private static ClientPlayerEntity clientPlayer = null;
private static boolean isServerCarpet = false;
public static String serverCarpetVersion;
public static final Identifier CARPET_CHANNEL = new Identifier("carpet:hello");
public static void gameJoined(ClientPlayerEntity player)
{
synchronized (sync)
{
clientPlayer = player;
// client didn't say hi back yet
if (isServerCarpet)
ClientNetworkHandler.respondHello();
}
}
public static void disconnect()
{
isServerCarpet = false;
clientPlayer = null;
CarpetServer.disconnect();
}
public static void setCarpet()
{
isServerCarpet = true;
}
public static ClientPlayerEntity getPlayer()
{
return clientPlayer;
}
public static boolean isCarpet()
{
return isServerCarpet;
}
}
@@ -0,0 +1,102 @@
package carpet.network;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.helpers.TickSpeed;
import carpet.settings.ParsedRule;
import io.netty.buffer.Unpooled;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.nbt.AbstractNumberTag;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket;
import net.minecraft.util.PacketByteBuf;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class ClientNetworkHandler
{
private static Map<String, BiConsumer<ClientPlayerEntity, Tag>> dataHandlers = new HashMap<String, BiConsumer<ClientPlayerEntity, Tag>>(){{
put("Rules", (p, t) -> {
CompoundTag ruleset = (CompoundTag)t;
for (String ruleName: ruleset.getKeys())
{
ParsedRule<?> rule = CarpetServer.settingsManager.getRule(ruleName);
if (rule == null)
CarpetSettings.LOG.error("Received unknown rule: "+ruleName);
else
{
CompoundTag ruleNBT = (CompoundTag) ruleset.get(ruleName);
String value = ruleNBT.getString("Value");
rule.set(null, value);
}
}
});
put("TickRate", (p, t) -> TickSpeed.tickrate(((AbstractNumberTag)t).getFloat(), false));
put("scShape", (p, t) -> { // deprecated
if (CarpetClient.shapes != null)
CarpetClient.shapes.addShape((CompoundTag)t);
});
put("scShapes", (p, t) -> {
if (CarpetClient.shapes != null)
CarpetClient.shapes.addShapes((ListTag) t);
});
}};
public static void handleData(PacketByteBuf data, ClientPlayerEntity player)
{
if (data != null)
{
int id = data.readVarInt();
if (id == CarpetClient.HI)
onHi(data);
if (id == CarpetClient.DATA)
onSyncData(data, player);
}
}
private static void onHi(PacketByteBuf data)
{
synchronized (CarpetClient.sync)
{
CarpetClient.setCarpet();
CarpetClient.serverCarpetVersion = data.readString(64);
if (CarpetSettings.carpetVersion.equals(CarpetClient.serverCarpetVersion))
{
CarpetSettings.LOG.info("Joined carpet server with matching carpet version");
}
else
{
CarpetSettings.LOG.warn("Joined carpet server with another carpet version: "+CarpetClient.serverCarpetVersion);
}
if (CarpetClient.getPlayer() != null)
respondHello();
}
}
public static void respondHello()
{
CarpetClient.getPlayer().networkHandler.sendPacket(new CustomPayloadC2SPacket(
CarpetClient.CARPET_CHANNEL,
(new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HELLO).writeString(CarpetSettings.carpetVersion)
));
}
private static void onSyncData(PacketByteBuf data, ClientPlayerEntity player)
{
CompoundTag compound = data.readCompoundTag();
if (compound == null) return;
for (String key: compound.getKeys())
{
if (dataHandlers.containsKey(key))
dataHandlers.get(key).accept(player, compound.get(key));
else
CarpetSettings.LOG.error("Unknown carpet data: "+key);
}
}
}
@@ -0,0 +1,178 @@
package carpet.network;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.helpers.TickSpeed;
import carpet.settings.ParsedRule;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.PacketByteBuf;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ServerNetworkHandler
{
private static Map<ServerPlayerEntity, String> remoteCarpetPlayers = new HashMap<>();
private static Set<ServerPlayerEntity> validCarpetPlayers = new HashSet<>();
public static void handleData(PacketByteBuf data, ServerPlayerEntity player)
{
if (data != null)
{
int id = data.readVarInt();
if (id == CarpetClient.HELLO)
onHello(player, data);
}
}
public static void onPlayerJoin(ServerPlayerEntity playerEntity)
{
if (!playerEntity.networkHandler.connection.isLocal())
{
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(
CarpetClient.CARPET_CHANNEL,
(new PacketByteBuf(Unpooled.buffer())).writeVarInt(CarpetClient.HI).writeString(CarpetSettings.carpetVersion)
));
}
else
{
validCarpetPlayers.add(playerEntity);
}
}
public static void onHello(ServerPlayerEntity playerEntity, PacketByteBuf packetData)
{
validCarpetPlayers.add(playerEntity);
String clientVersion = packetData.readString(64);
remoteCarpetPlayers.put(playerEntity, clientVersion);
if (clientVersion.equals(CarpetSettings.carpetVersion))
CarpetSettings.LOG.info("Player "+playerEntity.getName().getString()+" joined with a matching carpet client");
else
CarpetSettings.LOG.warn("Player "+playerEntity.getName().getString()+" joined with another carpet version: "+clientVersion);
DataBuilder data = DataBuilder.create().withTickRate();
CarpetServer.settingsManager.getRules().forEach(data::withRule);
playerEntity.networkHandler.sendPacket(new CustomPayloadS2CPacket(CarpetClient.CARPET_CHANNEL, data.build() ));
}
public static void updateRuleWithConnectedClients(ParsedRule<?> rule)
{
if (CarpetSettings.superSecretSetting) return;
for (ServerPlayerEntity player : remoteCarpetPlayers.keySet())
{
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
CarpetClient.CARPET_CHANNEL,
DataBuilder.create().withRule(rule).build()
));
}
}
public static void updateTickSpeedToConnectedPlayers()
{
if (CarpetSettings.superSecretSetting) return;
for (ServerPlayerEntity player : remoteCarpetPlayers.keySet())
{
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
CarpetClient.CARPET_CHANNEL,
DataBuilder.create().withTickRate().build()
));
}
}
public static void broadcastCustomCommand(String command, Tag data)
{
if (CarpetSettings.superSecretSetting) return;
for (ServerPlayerEntity player : validCarpetPlayers)
{
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
CarpetClient.CARPET_CHANNEL,
DataBuilder.create().withCustomNbt(command, data).build()
));
}
}
public static void sendCustomCommand(ServerPlayerEntity player, String command, Tag data)
{
if (isValidCarpetPlayer(player))
{
player.networkHandler.sendPacket(new CustomPayloadS2CPacket(
CarpetClient.CARPET_CHANNEL,
DataBuilder.create().withCustomNbt(command, data).build()
));
}
}
public static void onPlayerLoggedOut(ServerPlayerEntity player)
{
validCarpetPlayers.remove(player);
if (!player.networkHandler.connection.isLocal())
remoteCarpetPlayers.remove(player);
}
public static void close()
{
remoteCarpetPlayers.clear();
validCarpetPlayers.clear();
}
public static boolean isValidCarpetPlayer(ServerPlayerEntity player)
{
if (CarpetSettings.superSecretSetting) return false;
return validCarpetPlayers.contains(player);
}
private static class DataBuilder
{
private CompoundTag tag;
private static DataBuilder create()
{
return new DataBuilder();
}
private DataBuilder()
{
tag = new CompoundTag();
}
private DataBuilder withTickRate()
{
tag.putFloat("TickRate", TickSpeed.tickrate);
return this;
}
private DataBuilder withRule(ParsedRule<?> rule)
{
CompoundTag rules = (CompoundTag) tag.get("Rules");
if (rules == null)
{
rules = new CompoundTag();
tag.put("Rules", rules);
}
CompoundTag ruleNBT = new CompoundTag();
ruleNBT.putString("Value", rule.getAsString());
rules.put(rule.name, ruleNBT);
return this;
}
public DataBuilder withCustomNbt(String key, Tag value)
{
tag.put(key, value);
return this;
}
private PacketByteBuf build()
{
PacketByteBuf packetBuf = new PacketByteBuf(Unpooled.buffer());
packetBuf.writeVarInt(CarpetClient.DATA);
packetBuf.writeCompoundTag(tag);
return packetBuf;
}
}
}
@@ -12,6 +12,8 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value;
import carpet.utils.Messenger;
import net.minecraft.block.BlockState;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity;
import net.minecraft.server.network.ServerPlayerEntity;
@@ -50,6 +52,7 @@ import java.util.stream.Collectors;
public class CarpetEventServer
{
public final List<ScheduledCall> scheduledCalls = new LinkedList<>();
public final MinecraftServer server;
public static class Callback
{
@@ -125,7 +128,13 @@ public class CarpetEventServer
List<LazyValue> argv = argumentSupplier.get(); // empty for onTickDone
ServerCommandSource source = cmdSourceSupplier.get();
assert argv.size() == reqArgs;
callList.removeIf(call -> !CarpetServer.scriptServer.runas(source, call.host, call.function, argv)); // this actually does the calls
List<Callback> fails = new ArrayList<>();
for (Callback call: callList)
{
if (!CarpetServer.scriptServer.runas(source, call.host, call.function, argv))
fails.add(call);
}
for (Callback call : fails) callList.remove(call);
}
}
public boolean addEventCall(ServerCommandSource source, String hostName, String funName, Function<ScriptHost, Boolean> verifier)
@@ -479,6 +488,90 @@ public class CarpetEventServer
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
}
},
PLAYER_TAKES_DAMAGE("player_takes_damage", 4, false)
{
@Override
public void onDamage(Entity target, float amount, DamageSource source)
{
handler.call( () ->
{
return Arrays.asList(
((c, t) -> new EntityValue(target)),
((c, t) -> new NumericValue(amount)),
((c, t) ->new StringValue(source.getName())),
((c, t) -> source.getAttacker()==null?Value.NULL:new EntityValue(source.getAttacker()))
);
}, target::getCommandSource);
}
},
PLAYER_DEALS_DAMAGE("player_deals_damage", 3, false)
{
@Override
public void onDamage(Entity target, float amount, DamageSource source)
{
handler.call( () ->
{
return Arrays.asList(
((c, t) -> new EntityValue(source.getAttacker())),
((c, t) -> new NumericValue(amount)),
((c, t) -> new EntityValue(target))
);
}, () -> source.getAttacker().getCommandSource());
}
},
PLAYER_DIES("player_dies", 1, false)
{
@Override
public void onPlayerEvent(ServerPlayerEntity player)
{
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
}
},
PLAYER_RESPAWNS("player_respawns", 1, false)
{
@Override
public void onPlayerEvent(ServerPlayerEntity player)
{
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
}
},
PLAYER_CHANGES_DIMENSION("player_changes_dimension", 5, false)
{
@Override
public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, DimensionType fromDim, DimensionType dimTo)
{
// eligibility already checked in mixin
Value fromValue = ListValue.fromTriple(from.x, from.y, from.z);
Value toValue = (to == null)?Value.NULL:ListValue.fromTriple(to.x, to.y, to.z);
Value fromDimStr = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.DIMENSION_TYPE.getId(fromDim)));
Value toDimStr = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.DIMENSION_TYPE.getId(dimTo)));
handler.call( () -> Arrays.asList(
((c, t) -> new EntityValue(player)),
((c, t) -> fromValue),
((c, t) -> fromDimStr),
((c, t) -> toValue),
((c, t) -> toDimStr)
), player::getCommandSource);
}
},
PLAYER_CONNECTS("player_connects", 1, false) {
@Override
public void onPlayerEvent(ServerPlayerEntity player)
{
handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource);
}
},
PLAYER_DISCONNECTS("player_disconnects", 2, false) {
@Override
public void onPlayerMessage(ServerPlayerEntity player, String message)
{
handler.call( () -> Arrays.asList(
((c, t) -> new EntityValue(player)),
((c, t) -> new StringValue(message))
), player::getCommandSource);
}
},
STATISTICS("statistic", 4, false)
{
private <T> Identifier getStatId(Stat<T> stat)
@@ -543,6 +636,7 @@ public class CarpetEventServer
public void onTick() { }
public void onChunkGenerated(ServerWorld world, Chunk chunk) { }
public void onPlayerEvent(ServerPlayerEntity player) { }
public void onPlayerMessage(ServerPlayerEntity player, String message) { }
public void onPlayerStatistic(ServerPlayerEntity player, Stat<?> stat, int amount) { }
public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { }
public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) { }
@@ -551,13 +645,18 @@ public class CarpetEventServer
public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS) { }
public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) { }
public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, DimensionType fromDim, DimensionType dimTo) {}
public void onDamage(Entity target, float amount, DamageSource source) { }
public void onWorldEvent(ServerWorld world, BlockPos pos) { }
public void onWorldEventFlag(ServerWorld world, BlockPos pos, int flag) { }
}
public CarpetEventServer()
public CarpetEventServer(MinecraftServer server)
{
this.server = server;
for (Event e: Event.values())
e.handler.callList.clear();
}
File diff suppressed because it is too large. Load diff
@@ -96,7 +96,7 @@ public class CarpetScriptHost extends ScriptHost
public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function)
{
super.addUserDefinedFunction(ctx, module, funName, function);
// mcarpet
if (ctx.host.main != module) return; // not dealing with automatic imports / exports /configs / apps from imports
if (funName.startsWith("__")) // potential fishy activity
{
if (funName.startsWith("__on_")) // here we can make a determination if we want to only accept events from main module.
@@ -323,7 +323,7 @@ public class CarpetScriptHost extends ScriptHost
{
try
{
callUDF(BlockPos.ORIGIN, CarpetServer.minecraft_server.getCommandSource(), closing, Collections.emptyList());
callUDF(BlockPos.ORIGIN, scriptServer.server.getCommandSource(), closing, Collections.emptyList());
}
catch (InvalidCallbackException ignored)
{
@@ -334,8 +334,8 @@ public class CarpetScriptHost extends ScriptHost
FunctionValue userClosing = value.getFunction("__on_close");
if (userClosing != null)
{
ServerPlayerEntity player = CarpetServer.minecraft_server.getPlayerManager().getPlayer(key);
ServerCommandSource source = (player != null)?player.getCommandSource():CarpetServer.minecraft_server.getCommandSource();
ServerPlayerEntity player = scriptServer.server.getPlayerManager().getPlayer(key);
ServerCommandSource source = (player != null)?player.getCommandSource():scriptServer.server.getCommandSource();
try
{
((CarpetScriptHost) value).callUDF(BlockPos.ORIGIN, source, userClosing, Collections.emptyList());
@@ -347,7 +347,7 @@ public class CarpetScriptHost extends ScriptHost
});
String markerName = CarpetExpression.MARKER_STRING+"_"+((getName()==null)?"":getName());
for (ServerWorld world : CarpetServer.minecraft_server.getWorlds())
for (ServerWorld world : scriptServer.server.getWorlds())
{
for (Entity e : world.getEntities(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName)))
{
@@ -361,32 +361,31 @@ public class CarpetScriptHost extends ScriptHost
private void dumpState()
{
main.saveData(null, globalState);
Module.saveData(main, null, globalState, false);
}
private Tag loadState()
{
return main.getData(null);
return Module.getData(main, null, false);
}
public Tag getGlobalState(String file)
public Tag readFileTag(String file, boolean isShared)
{
if (getName() == null ) return null;
if (getName() == null && !isShared) return null;
if (file != null)
return main.getData(file);
return Module.getData(main, file, isShared);
if (parent == null)
return globalState;
return ((CarpetScriptHost)parent).globalState;
}
public void setGlobalState(Tag tag, String file)
public boolean writeTagFile(Tag tag, String file, boolean isShared)
{
if (getName() == null ) return;
if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
if (file!= null)
{
main.saveData(file, tag);
return;
return Module.saveData(main, file, tag, isShared);
}
CarpetScriptHost responsibleHost = (parent != null)?(CarpetScriptHost) parent:this;
@@ -396,8 +395,28 @@ public class CarpetScriptHost extends ScriptHost
responsibleHost.dumpState();
responsibleHost.saveTimeout = 200;
}
return true;
}
public boolean removeResourceFile(String resource, boolean isShared, String type)
{
if (getName() == null && !isShared) return false; //
return Module.dropExistingFile(main, resource, type.equals("nbt")?"nbt":"txt", isShared);
}
public boolean appendLogFile(String resource, boolean isShared, String type, List<String> data)
{
if (getName() == null && !isShared) return false; // if belongs to an app, cannot be default host.
return Module.appendToTextFile(main, resource, type, isShared, data);
}
public List<String> readTextResource(String resource, boolean isShared)
{
if (getName() == null && !isShared) return null; //
return Module.listFile(main, resource, "txt", isShared);
}
public void tick()
{
if (this.saveTimeout > 0)
@@ -491,4 +510,9 @@ public class CarpetScriptHost extends ScriptHost
{
handleErrorWithStack(message, new CarpetExpressionException(exc.getMessage(), exc.stack));
}
public CarpetScriptServer getScriptServer()
{
return scriptServer;
}
}
@@ -15,6 +15,7 @@ import carpet.utils.Messenger;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.util.math.BlockPos;
@@ -34,6 +35,7 @@ import static net.minecraft.server.command.CommandManager.literal;
public class CarpetScriptServer
{
//make static for now, but will change that later:
public final MinecraftServer server;
public final CarpetScriptHost globalHost;
public final Map<String, CarpetScriptHost> modules;
public long tickStart;
@@ -47,16 +49,18 @@ public class CarpetScriptServer
add(new BundledModule("event_test", false));
add(new BundledModule("stats_test", false));
add(new BundledModule("math", true));
add(new BundledModule("chunk_display", false));
}};
public CarpetScriptServer()
public CarpetScriptServer(MinecraftServer server)
{
this.server = server;
ScriptHost.systemGlobals.clear();
events = new CarpetEventServer();
events = new CarpetEventServer(server);
modules = new HashMap<>();
tickStart = 0L;
stopAll = false;
holyMoly = CarpetServer.minecraft_server.getCommandManager().getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName).collect(Collectors.toSet());
holyMoly = server.getCommandManager().getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName).collect(Collectors.toSet());
globalHost = CarpetScriptHost.create(this, null, false, null);
}
@@ -64,10 +68,10 @@ public class CarpetScriptServer
{
if (CarpetSettings.scriptsAutoload)
{
Messenger.m(CarpetServer.minecraft_server.getCommandSource(), "Auto-loading world scarpet apps");
Messenger.m(server.getCommandSource(), "Auto-loading world scarpet apps");
for (String moduleName: listAvailableModules(false))
{
addScriptHost(CarpetServer.minecraft_server.getCommandSource(), moduleName, true, true);
addScriptHost(server.getCommandSource(), moduleName, true, true);
}
}
@@ -75,8 +79,8 @@ public class CarpetScriptServer
public Module getModule(String name, boolean allowLibraries)
{
File folder = CarpetServer.minecraft_server.getLevelStorage().resolveFile(
CarpetServer.minecraft_server.getLevelName(), "scripts");
File folder = server.getLevelStorage().resolveFile(
server.getLevelName(), "scripts");
File[] listOfFiles = folder.listFiles();
if (listOfFiles != null)
for (File script : listOfFiles)
@@ -110,8 +114,8 @@ public class CarpetScriptServer
if (!mi.isLibrary()) moduleNames.add(mi.getName());
}
}
File folder = CarpetServer.minecraft_server.getLevelStorage().resolveFile(
CarpetServer.minecraft_server.getLevelName(), "scripts");
File folder = server.getLevelStorage().resolveFile(
server.getLevelName(), "scripts");
File[] listOfFiles = folder.listFiles();
if (listOfFiles == null)
return moduleNames;
@@ -223,7 +227,7 @@ public class CarpetScriptServer
})));
}
Messenger.m(source, "gi "+hostName+" app loaded with /"+hostName+" command");
CarpetServer.minecraft_server.getCommandManager().getDispatcher().register(command);
server.getCommandManager().getDispatcher().register(command);
CarpetServer.settingsManager.notifyPlayersCommandsChanged();
}
+33 -37
View File
@@ -11,6 +11,7 @@ import carpet.script.Fluff.QuadFunction;
import carpet.script.Fluff.QuinnFunction;
import carpet.script.Fluff.SexFunction;
import carpet.script.Fluff.TriFunction;
import carpet.script.argument.FunctionArgument;
import carpet.script.bundled.Module;
import carpet.script.exception.BreakStatement;
import carpet.script.exception.ContinueStatement;
@@ -417,6 +418,12 @@ public class Expression
return result;
}
public void alias(String copy, String original)
{
functions.put(copy, functions.get(original));
}
private void UserDefinedFunctionsAndControlFlow() // public just to get the javadoc right
{
// artificial construct to handle user defined functions and function definitions
@@ -439,19 +446,9 @@ public class Expression
//lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes
if (t != Context.SIGNATURE) // just call the function
{
Value functionValue = lv.get(0).evalValue(c);
if (!(functionValue instanceof FunctionValue))
{
String name = functionValue.getString();
functionValue = c.host.getAssertFunction(module, name);
}
List<LazyValue> lvargs = new ArrayList<>(lv.size()-1);
for (int i=1; i< lv.size(); i++)
{
lvargs.add(lv.get(i));
}
FunctionValue fun = (FunctionValue)functionValue;
Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), lvargs).evalValue(c);
FunctionArgument functionArgument = FunctionArgument.findIn(c, module, lv, 0, false, true);
FunctionValue fun = functionArgument.function;
Value retval = fun.callInContext(expr, c, t, fun.getExpression(), fun.getToken(), functionArgument.args).evalValue(c);
return (cc, tt) -> retval; ///!!!! dono might need to store expr and token in statics? (e? t?)
}
// gimme signature
@@ -657,9 +654,18 @@ public class Expression
throw new InternalExpressionException("Failed to resolve left hand side of the += operation");
}
Value key = ((LContainerValue) v1).getAddress();
Value res = cvi.get(key).add(v2);
cvi.put(key, res);
return (cc, tt) -> res;
Value value = cvi.get(key);
if (value instanceof ListValue || value instanceof MapValue)
{
((AbstractListValue) value).append(v2);
return (cc, tt) -> value;
}
else
{
Value res = value.add(v2);
cvi.put(key, res);
return (cc, tt) -> res;
}
}
v1.assertAssignable();
String varname = v1.getVariable();
@@ -1114,7 +1120,7 @@ public class Expression
{
Value rval= lv.get(0).evalValue(c);
if (!(rval instanceof AbstractListValue))
throw new InternalExpressionException("Second argument of 'for' function should be a list or iterator");
throw new InternalExpressionException("First argument of 'for' function should be a list or iterator");
Iterator<Value> iterator = ((AbstractListValue) rval).iterator();
LazyValue expr = lv.get(1);
//scoping
@@ -1558,7 +1564,7 @@ public class Expression
if (lv.size() == 1)
return new StringValue(format);
int argIndex = 1;
if (lv.get(1) instanceof ListValue)
if (lv.get(1) instanceof ListValue && lv.size() == 2)
{
lv = ((ListValue) lv.get(1)).getItems();
argIndex = 0;
@@ -1861,26 +1867,10 @@ public class Expression
addLazyFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> {
if (lv.size() == 0)
throw new InternalExpressionException("'task' requires at least function to call as a parameter");
Value funcDesc = lv.get(0).evalValue(c);
if (!(funcDesc instanceof FunctionValue))
{
String name = funcDesc.getString();
funcDesc = c.host.getAssertFunction(module, name);
}
FunctionValue fun = (FunctionValue)funcDesc;
int extraargs = lv.size() - fun.getArguments().size();
if (extraargs != 1 && extraargs != 2)
{
throw new InternalExpressionException("Function takes "+fun.getArguments().size()+" arguments.");
}
List<LazyValue> lvargs = new ArrayList<>();
for (int i=0; i< fun.getArguments().size(); i++)
{
lvargs.add(lv.get(i+1));
}
FunctionArgument functionArgument = FunctionArgument.findIn(c, module, lv, 0, true, false);
Value queue = Value.NULL;
if (extraargs == 2) queue = lv.get(lv.size()-1).evalValue(c);
ThreadValue thread = new ThreadValue(queue, fun, expr, tok, c, lvargs);
if (lv.size() > functionArgument.offset) queue = lv.get(functionArgument.offset).evalValue(c);
ThreadValue thread = new ThreadValue(queue, functionArgument.function, expr, tok, c, functionArgument.args);
Thread.yield();
return (cc, tt) -> thread;
});
@@ -1906,6 +1896,12 @@ public class Expression
return ((ThreadValue) v).join();
});
addLazyFunction("task_dock", 1, (c, t, lv) -> {
// pass through placeholder
// implmenetation should dock the task on the main thread.
return lv.get(0);
});
addUnaryFunction("task_completed", (v) -> {
if (!(v instanceof ThreadValue))
throw new InternalExpressionException("'task_completed' could only be used with a task value");
+8 -3
View File
@@ -173,9 +173,9 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
}
else if (nextChar == 't')
{
throw new ExpressionException(context, this.expression, token,
"Tab character is not supported");
//token.append('\t');
//throw new ExpressionException(context, this.expression, token,
// "Tab character is not supported");
token.append('\t');
}
else if (nextChar == 'r')
{
@@ -201,6 +201,11 @@ public class Tokenizer implements Iterator<Tokenizer.Token>
{
token.append(input.charAt(pos++));
linepos++;
if (ch=='\n')
{
lineno++;
linepos = 0;
}
if (pos == input.length() && expression != null && context != null)
throw new ExpressionException(context, this.expression, token, "Program truncated");
}
@@ -0,0 +1,10 @@
package carpet.script.argument;
public abstract class Argument
{
public int offset;
protected Argument(int offset)
{
this.offset = offset;
}
}
@@ -0,0 +1,144 @@
package carpet.script.argument;
import carpet.script.CarpetContext;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue;
import carpet.script.value.ListValue;
import carpet.script.value.NullValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
import net.minecraft.util.math.BlockPos;
import java.util.List;
public class BlockArgument extends Argument
{
public final BlockValue block;
private BlockArgument(BlockValue b, int o)
{
super(o);
block = b;
}
public static BlockArgument findIn(CarpetContext c, List<LazyValue> params, int offset)
{
return findIn(c, params,offset, false, false);
}
public static BlockArgument findIn(CarpetContext c, List<LazyValue> params, int offset, boolean acceptString)
{
return findIn(c, params,offset, acceptString, false);
}
public static BlockArgument findIn(CarpetContext c, List<LazyValue> params, int offset, boolean acceptString, boolean optional)
{
try
{
Value v1 = params.get(0 + offset).evalValue(c);
//add conditional from string name
if (optional && v1 instanceof NullValue)
{
return new BlockArgument(null, 1+offset);
}
if (acceptString && v1 instanceof StringValue)
{
return new BlockArgument(BlockValue.fromString(v1.getString()), 1+offset);
}
if (v1 instanceof BlockValue)
{
return new BlockArgument(((BlockValue) v1), 1+offset);
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
int xpos = (int) NumericValue.asNumber(args.get(0)).getLong();
int ypos = (int) NumericValue.asNumber(args.get(1)).getLong();
int zpos = (int) NumericValue.asNumber(args.get(2)).getLong();
return new BlockArgument(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
1+offset);
}
int xpos = (int) NumericValue.asNumber(v1).getLong();
int ypos = (int) NumericValue.asNumber( params.get(1 + offset).evalValue(c)).getLong();
int zpos = (int) NumericValue.asNumber( params.get(2 + offset).evalValue(c)).getLong();
return new BlockArgument(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
3+offset
);
}
catch (IndexOutOfBoundsException e)
{
throw handleError(optional, acceptString);
}
}
public static BlockArgument findInValues(CarpetContext c, List<Value> params, int offset, boolean acceptString, boolean optional)
{
try
{
Value v1 = params.get(0 + offset);
//add conditional from string name
if (optional && v1 instanceof NullValue)
{
return new BlockArgument(null, 1+offset);
}
if (acceptString && v1 instanceof StringValue)
{
return new BlockArgument(BlockValue.fromString(v1.getString()), 1+offset);
}
if (v1 instanceof BlockValue)
{
return new BlockArgument(((BlockValue) v1), 1+offset);
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
int xpos = (int) NumericValue.asNumber(args.get(0)).getLong();
int ypos = (int) NumericValue.asNumber(args.get(1)).getLong();
int zpos = (int) NumericValue.asNumber(args.get(2)).getLong();
return new BlockArgument(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
1+offset);
}
int xpos = (int) NumericValue.asNumber(v1).getLong();
int ypos = (int) NumericValue.asNumber( params.get(1 + offset)).getLong();
int zpos = (int) NumericValue.asNumber( params.get(2 + offset)).getLong();
return new BlockArgument(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
3+offset
);
}
catch (IndexOutOfBoundsException e)
{
throw handleError(optional, acceptString);
}
}
private static InternalExpressionException handleError(boolean optional, boolean acceptString)
{
String message = "Block-type argument should be defined either by three coordinates (a triple or by three arguments), or a block value";
if (acceptString)
message+=", or a string with block description";
if (optional)
message+=", or null";
return new InternalExpressionException(message);
}
}
@@ -0,0 +1,60 @@
package carpet.script.argument;
import carpet.script.CarpetContext;
import carpet.script.Context;
import carpet.script.LazyValue;
import carpet.script.bundled.Module;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.FunctionValue;
import carpet.script.value.Value;
import java.util.ArrayList;
import java.util.List;
public class FunctionArgument extends Argument
{
public FunctionValue function;
public List<LazyValue> args;
private FunctionArgument(FunctionValue function, int offset, List<LazyValue> args)
{
super(offset);
this.function = function;
this.args = args;
}
public static FunctionArgument findIn(Context c, Module module, List<LazyValue> params, int offset, boolean prematureEvaluation, boolean strict)
{
Value functionValue = params.get(offset).evalValue(c);
if (!(functionValue instanceof FunctionValue))
{
String name = functionValue.getString();
functionValue = c.host.getAssertFunction(module, name);
}
FunctionValue fun = (FunctionValue)functionValue;
int argsize = fun.getArguments().size();
int extraargs = params.size() - argsize - offset - 1;
if (extraargs < 0 || (extraargs > 0 && strict))
throw new InternalExpressionException("Function "+fun.getPrettyString()+" requires "+fun.getArguments().size()+" arguments");
List<LazyValue> lvargs = new ArrayList<>();
if (prematureEvaluation)
{
for (int i = 0; i < argsize; i++)
{
Value arg = params.get(offset + 1 + i).evalValue(c);
lvargs.add((cc, tt) -> arg);
}
}
else
{
for (int i = 0; i < argsize; i++) lvargs.add(params.get(offset + 1 + i));
}
return new FunctionArgument(fun, offset+1+argsize, lvargs);
}
public List<Value> resolveArgs(Context context, Integer type)
{
List<Value> res = new ArrayList<>(args.size());
for (LazyValue arg : args) res.add(arg.evalValue(context, type));
return res;
}
}
@@ -0,0 +1,140 @@
package carpet.script.argument;
import carpet.script.CarpetContext;
import carpet.script.LazyValue;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.BlockValue;
import carpet.script.value.ListValue;
import carpet.script.value.NumericValue;
import carpet.script.value.Value;
import net.minecraft.util.math.Vec3d;
import java.util.List;
public class Vector3Argument extends Argument
{
public Vec3d vec;
public final double yaw;
public final double pitch;
public boolean fromBlock;
private Vector3Argument(Vec3d v, int o)
{
super(o);
this.vec = v;
this.yaw = 0.0D;
this.pitch = 0.0D;
}
private Vector3Argument(Vec3d v, int o, double y, double p)
{
super(o);
this.vec = v;
this.yaw = y;
this.pitch = p;
}
private Vector3Argument fromBlock()
{
fromBlock = true;
return this;
}
public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset)
{
return findIn(c,params, offset, false);
}
public static Vector3Argument findIn(CarpetContext c, List<LazyValue> params, int offset, boolean optionalDirection)
{
try
{
Value v1 = params.get(0 + offset).evalValue(c);
if (v1 instanceof BlockValue)
{
return (new Vector3Argument(new Vec3d(((BlockValue) v1).getPos()).add(0.5,0.5,0.5), 1+offset)).fromBlock();
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
Vec3d pos = new Vec3d(
NumericValue.asNumber(args.get(0)).getDouble(),
NumericValue.asNumber(args.get(1)).getDouble(),
NumericValue.asNumber(args.get(2)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
if (args.size()>3 && optionalDirection)
{
yaw = NumericValue.asNumber(args.get(3)).getDouble();
pitch = NumericValue.asNumber(args.get(4)).getDouble();
}
return new Vector3Argument(pos,offset+1, yaw, pitch);
}
Vec3d pos = new Vec3d(
NumericValue.asNumber(v1).getDouble(),
NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(),
NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
int eatenLength = 3;
if (params.size()>3+offset && optionalDirection)
{
yaw = NumericValue.asNumber(params.get(3 + offset).evalValue(c)).getDouble();
pitch = NumericValue.asNumber(params.get(4 + offset).evalValue(c)).getDouble();
eatenLength = 5;
}
return new Vector3Argument(pos,offset+eatenLength, yaw, pitch);
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("Position argument should be defined either by three coordinates (a triple or by three arguments), or a positioned block value");
}
}
public static Vector3Argument findIn(List<Value> params, int offset, boolean optionalDirection)
{
try
{
Value v1 = params.get(0 + offset);
if (v1 instanceof BlockValue)
{
return (new Vector3Argument(new Vec3d(((BlockValue) v1).getPos()).add(0.5,0.5,0.5), 1+offset)).fromBlock();
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
Vec3d pos = new Vec3d(
NumericValue.asNumber(args.get(0)).getDouble(),
NumericValue.asNumber(args.get(1)).getDouble(),
NumericValue.asNumber(args.get(2)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
if (args.size()>3 && optionalDirection)
{
yaw = NumericValue.asNumber(args.get(3)).getDouble();
pitch = NumericValue.asNumber(args.get(4)).getDouble();
}
return new Vector3Argument(pos,offset+1, yaw, pitch);
}
Vec3d pos = new Vec3d(
NumericValue.asNumber(v1).getDouble(),
NumericValue.asNumber(params.get(1 + offset)).getDouble(),
NumericValue.asNumber(params.get(2 + offset)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
int eatenLength = 3;
if (params.size()>3+offset && optionalDirection)
{
yaw = NumericValue.asNumber(params.get(3 + offset)).getDouble();
pitch = NumericValue.asNumber(params.get(4 + offset)).getDouble();
eatenLength = 5;
}
return new Vector3Argument(pos,offset+eatenLength, yaw, pitch);
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("Position argument should be defined either by three coordinates (a triple or by three arguments), or a positioned block value");
}
}
}
@@ -1,17 +1,24 @@
package carpet.script.bundled;
import carpet.CarpetServer;
import net.minecraft.nbt.PositionTracker;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.TagReaders;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class FileModule extends Module
@@ -74,7 +81,7 @@ public class FileModule extends Module
}
//copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write
public static void write(Tag tag_1, File file)
public static boolean write(Tag tag_1, File file)
{
File file_2 = new File(file.getAbsolutePath() + "_tmp");
if (file_2.exists()) file_2.delete();
@@ -90,9 +97,50 @@ public class FileModule extends Module
}
catch (IOException e)
{
return;
return false;
}
if (file.exists()) file.delete();
if (!file.exists()) file_2.renameTo(file);
return true;
}
public static boolean appendText(File filePath, boolean addNewLines, List<String> data)
{
try
{
OutputStream out = Files.newOutputStream(filePath.toPath(), StandardOpenOption.APPEND, StandardOpenOption.CREATE);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
{
for (String line: data)
{
writer.append(line);
if (addNewLines) writer.newLine();
}
}
}
catch (IOException e)
{
return false;
}
return true;
}
public static List<String> listFileContent(File filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath.toPath(), StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
break;
result.add(line.replaceAll("[\n\r]+",""));
}
return result;
}
catch (IOException e)
{
return null;
}
}
}
+57 -23
View File
@@ -4,42 +4,76 @@ import carpet.CarpetServer;
import net.minecraft.nbt.Tag;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import static carpet.script.bundled.FileModule.read;
import static carpet.script.bundled.FileModule.write;
import java.util.List;
public abstract class Module
{
public abstract String getName();
public abstract String getCode();
public abstract boolean isLibrary();
public final static Object writeIOSync = new Object();
public Tag getData(String file)
private static String getDescriptor(Module module, String file, boolean isShared)
{
File dataFile = CarpetServer.minecraft_server.getLevelStorage().resolveFile(
CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt");
if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null;
return read(dataFile);
if (isShared)
{
return "shared/"+file;
}
else if (module != null && module.getName() != null) // appdata
{
return module.getName()+".data"+(file==null?"":"/"+file);
}
else
{
throw new RuntimeException("Invalid file descriptor: "+file);
}
}
public void saveData(String file, Tag globalState)
public static Tag getData(Module module, String file, boolean isShared)
{
File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile(
CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt");
if (!Files.exists(dataFile.toPath().getParent()))
{
try
{
Files.createDirectory(dataFile.toPath().getParent());
}
catch (IOException ignored)
{
}
}
write(globalState, dataFile);
File 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); }
}
public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
{
File 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); }
}
public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message)
{
File 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); }
}
public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared)
{
File dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return false;
synchronized (writeIOSync) { return dataFile.delete(); }
}
public static List<String> listFile(Module module, String resourceName, String ext, boolean isShared)
{
File dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return null;
if (!dataFile.exists()) return null;
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
}
private static File resolveResource(Module module, String resourceName, String ext, boolean isShared)
{
if (!isShared && (module == null || module.getName() == null)) return null;
return CarpetServer.minecraft_server.getLevelStorage().resolveFile(
CarpetServer.minecraft_server.getLevelName(), "scripts/"+getDescriptor(module, resourceName, isShared)+"."+ext);
}
@Override
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,790 @@
package carpet.script.utils;
import carpet.CarpetSettings;
import com.mojang.blaze3d.systems.RenderSystem;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.IWorld;
import net.minecraft.world.dimension.DimensionType;
import org.lwjgl.opengl.GL11;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class ShapesRenderer
{
private final Map<DimensionType, Long2ObjectOpenHashMap<RenderedShape<? extends ShapeDispatcher.ExpiringShape>>> shapes;
private MinecraftClient client;
private Map<String, BiFunction<MinecraftClient, ShapeDispatcher.ExpiringShape, RenderedShape<? extends ShapeDispatcher.ExpiringShape >>> renderedShapes
= new HashMap<String, BiFunction<MinecraftClient, ShapeDispatcher.ExpiringShape, RenderedShape<? extends ShapeDispatcher.ExpiringShape>>>()
{{
put("line", RenderedLine::new);
put("box", RenderedBox::new);
put("sphere", RenderedSphere::new);
put("cylinder", RenderedCylinder::new);
}};
public ShapesRenderer(MinecraftClient minecraftClient)
{
this.client = minecraftClient;
shapes = new HashMap<>();
shapes.put(DimensionType.OVERWORLD, new Long2ObjectOpenHashMap<>());
shapes.put(DimensionType.THE_NETHER, new Long2ObjectOpenHashMap<>());
shapes.put(DimensionType.THE_END, new Long2ObjectOpenHashMap<>());
}
public void render(Camera camera, float partialTick)
{
IWorld iWorld = this.client.world;
DimensionType dimensionType = iWorld.getDimension().getType();
if (shapes.get(dimensionType).isEmpty()) return;
long currentTime = client.world.getTime();
RenderSystem.disableTexture();
RenderSystem.enableDepthTest();
RenderSystem.depthFunc(515);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
//RenderSystem.blendFuncSeparate(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE, GlStateManager.SrcFactor.ONE, GlStateManager.DstFactor.ZERO);
//RenderSystem.shadeModel(7425);
RenderSystem.shadeModel(GL11.GL_FLAT);
RenderSystem.enableAlphaTest();
RenderSystem.defaultAlphaFunc();
//RenderSystem.alphaFunc(GL11.GL_GREATER, 0.005F);
RenderSystem.disableCull();
RenderSystem.disableLighting();
RenderSystem.depthMask(false);
//RenderSystem.polygonOffset(-3f, -3f);
//RenderSystem.enablePolygonOffset();
//Entity entity = this.client.gameRenderer.getCamera().getFocusedEntity();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferBuilder = tessellator.getBuffer();
// render
double cameraX = camera.getPos().x;
double cameraY = camera.getPos().y;
double cameraZ = camera.getPos().z;
synchronized (shapes)
{
shapes.get(dimensionType).long2ObjectEntrySet().removeIf(
entry -> entry.getValue().isExpired(currentTime)
);
shapes.get(dimensionType).values().forEach(
s ->
{
if ( s.shouldRender(dimensionType))
s.renderFaces(tessellator, bufferBuilder, cameraX, cameraY, cameraZ, partialTick);
}
);
//lines
shapes.get(dimensionType).values().forEach(
s -> {
if ( s.shouldRender(dimensionType))
s.renderLines(tessellator, bufferBuilder, cameraX, cameraY, cameraZ, partialTick);
}
);
}
RenderSystem.enableCull();
RenderSystem.depthMask(true);
RenderSystem.lineWidth(1.0F);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableTexture();
RenderSystem.shadeModel(7424);
}
public void addShapes(ListTag tag)
{
for (int i=0, count = tag.size(); i < count; i++)
{
addShape(tag.getCompound(i));
}
}
public void addShape(CompoundTag tag)
{
ShapeDispatcher.ExpiringShape shape = ShapeDispatcher.fromTag(tag);
if (shape == null) return;
BiFunction<MinecraftClient, ShapeDispatcher.ExpiringShape, RenderedShape<? extends ShapeDispatcher.ExpiringShape >> shapeFactory;
shapeFactory = renderedShapes.get(tag.getString("shape"));
if (shapeFactory == null)
{
CarpetSettings.LOG.info("Unrecognized shape: "+tag.getString("shape"));
}
else
{
RenderedShape<?> rshape = shapeFactory.apply(client, shape);
DimensionType dim = shape.shapeDimension;
long key = rshape.key();
synchronized (shapes)
{
RenderedShape<?> existing = shapes.get(dim).get(key);
if (existing != null)
{ // promoting previous shape
existing.expiryTick = rshape.expiryTick;
}
else
{
shapes.get(dim).put(key, rshape);
}
}
}
}
public void reset()
{
synchronized (shapes)
{
shapes.values().forEach(Long2ObjectOpenHashMap::clear);
}
}
public abstract static class RenderedShape<T extends ShapeDispatcher.ExpiringShape>
{
protected T shape;
protected MinecraftClient client;
long expiryTick;
double renderEpsilon = 0;
public abstract void renderLines(Tessellator tessellator, BufferBuilder builder, double cx, double cy, double cz, float partialTick );
public void renderFaces(Tessellator tessellator, BufferBuilder builder, double cx, double cy, double cz, float partialTick ) {}
protected RenderedShape(MinecraftClient client, T shape)
{
this.shape = shape;
expiryTick = client.world.getTime()+shape.getExpiry();
renderEpsilon = (3+((double)shape.key())/Long.MAX_VALUE)/1000;
this.client = client;
}
public boolean isExpired(long currentTick)
{
return expiryTick < currentTick;
}
public long key()
{
return shape.key();
};
public boolean shouldRender(DimensionType dim)
{
if (shape.followEntity <=0 ) return true;
if (client.world == null) return false;
if (client.world.getEntityById(shape.followEntity) == null) return false;
return true;
}
}
public static class RenderedBox extends RenderedShape<ShapeDispatcher.Box>
{
private RenderedBox(MinecraftClient client, ShapeDispatcher.ExpiringShape shape)
{
super(client, (ShapeDispatcher.Box)shape);
}
@Override
public void renderLines(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.a == 0.0) return;
Vec3d v1 = shape.relativiseRender(client.world, shape.from, partialTick);
Vec3d v2 = shape.relativiseRender(client.world, shape.to, partialTick);
RenderSystem.lineWidth(shape.lineWidth);
drawBoxWireGLLines(tessellator, bufferBuilder,
(float)(v1.x-cx-renderEpsilon), (float)(v1.y-cy-renderEpsilon), (float)(v1.z-cz-renderEpsilon),
(float)(v2.x-cx+renderEpsilon), (float)(v2.y-cy+renderEpsilon), (float)(v2.z-cz+renderEpsilon),
v1.x!=v2.x, v1.y!=v2.y, v1.z!=v2.z,
shape.r, shape.g, shape.b, shape.a, shape.r, shape.g, shape.b
);
}
@Override
public void renderFaces(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.fa == 0.0) return;
Vec3d v1 = shape.relativiseRender(client.world, shape.from, partialTick);
Vec3d v2 = shape.relativiseRender(client.world, shape.to, partialTick);
RenderSystem.lineWidth(1.0F);
drawBoxFaces(tessellator, bufferBuilder,
(float)(v1.x-cx-renderEpsilon), (float)(v1.y-cy-renderEpsilon), (float)(v1.z-cz-renderEpsilon),
(float)(v2.x-cx+renderEpsilon), (float)(v2.y-cy+renderEpsilon), (float)(v2.z-cz+renderEpsilon),
v1.x!=v2.x, v1.y!=v2.y, v1.z!=v2.z,
shape.fr, shape.fg, shape.fb, shape.fa
);
}
}
public static class RenderedLine extends RenderedShape<ShapeDispatcher.Line>
{
public RenderedLine(MinecraftClient client, ShapeDispatcher.ExpiringShape shape)
{
super(client, (ShapeDispatcher.Line)shape);
}
@Override
public void renderLines(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
Vec3d v1 = shape.relativiseRender(client.world, shape.from, partialTick);
Vec3d v2 = shape.relativiseRender(client.world, shape.to, partialTick);
RenderSystem.lineWidth(shape.lineWidth);
drawLine(tessellator, bufferBuilder,
(float)(v1.x-cx-renderEpsilon), (float)(v1.y-cy-renderEpsilon), (float)(v1.z-cz-renderEpsilon),
(float)(v2.x-cx+renderEpsilon), (float)(v2.y-cy+renderEpsilon), (float)(v2.z-cz+renderEpsilon),
shape.r, shape.g, shape.b, shape.a
);
}
}
public static class RenderedSphere extends RenderedShape<ShapeDispatcher.Sphere>
{
public RenderedSphere(MinecraftClient client, ShapeDispatcher.ExpiringShape shape)
{
super(client, (ShapeDispatcher.Sphere)shape);
}
@Override
public void renderLines(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.a == 0.0) return;
Vec3d vc = shape.relativiseRender(client.world, shape.center, partialTick);
RenderSystem.lineWidth(shape.lineWidth);
drawSphereWireframe(tessellator, bufferBuilder,
(float)(vc.x-cx), (float)(vc.y-cy), (float)(vc.z-cz),
(float)(shape.radius+renderEpsilon), shape.subdivisions,
shape.r, shape.g, shape.b, shape.a);
}
@Override
public void renderFaces(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.fa == 0.0) return;
Vec3d vc = shape.relativiseRender(client.world, shape.center, partialTick);
RenderSystem.lineWidth(1.0f);
drawSphereFaces(tessellator, bufferBuilder,
(float)(vc.x-cx), (float)(vc.y-cy), (float)(vc.z-cz),
(float)(shape.radius+renderEpsilon), shape.subdivisions,
shape.fr, shape.fg, shape.fb, shape.fa);
}
}
public static class RenderedCylinder extends RenderedShape<ShapeDispatcher.Cylinder>
{
public RenderedCylinder(MinecraftClient client, ShapeDispatcher.ExpiringShape shape)
{
super(client, (ShapeDispatcher.Cylinder)shape);
}
@Override
public void renderLines(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.a == 0.0) return;
Vec3d vc = shape.relativiseRender(client.world, shape.center, partialTick);
RenderSystem.lineWidth(shape.lineWidth);
double dir = MathHelper.sign(shape.height);
drawCylinderWireframe(tessellator, bufferBuilder,
(float) (vc.x - cx - dir * renderEpsilon), (float) (vc.y - cy - dir * renderEpsilon), (float) (vc.z - cz - dir * renderEpsilon),
(float) (shape.radius + renderEpsilon), (float) (shape.height + 2*dir*renderEpsilon), shape.axis,
shape.subdivisions, shape.radius == 0,
shape.r, shape.g, shape.b, shape.a);
}
@Override
public void renderFaces(Tessellator tessellator, BufferBuilder bufferBuilder, double cx, double cy, double cz, float partialTick)
{
if (shape.fa == 0.0) return;
Vec3d vc = shape.relativiseRender(client.world, shape.center, partialTick);
RenderSystem.lineWidth(1.0f);
double dir = MathHelper.sign(shape.height);
drawCylinderFaces(tessellator, bufferBuilder,
(float) (vc.x - cx - dir * renderEpsilon), (float) (vc.y - cy - dir * renderEpsilon), (float) (vc.z - cz - dir * renderEpsilon),
(float) (shape.radius + renderEpsilon), (float) (shape.height + 2*dir*renderEpsilon), shape.axis,
shape.subdivisions, shape.radius == 0,
shape.fr, shape.fg, shape.fb, shape.fa);
}
}
// some raw shit
public static void drawLine(Tessellator tessellator, BufferBuilder builder, float x1, float y1, float z1, float x2, float y2, float z2, float red1, float grn1, float blu1, float alpha) {
builder.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR); // 3
builder.vertex(x1, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
tessellator.draw();
}
public static void drawBoxWireGLLines(
Tessellator tessellator, BufferBuilder builder,
float x1, float y1, float z1,
float x2, float y2, float z2,
boolean xthick, boolean ythick, boolean zthick,
float red1, float grn1, float blu1, float alpha, float red2, float grn2, float blu2)
{
builder.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR); // 3
if (xthick)
{
builder.vertex(x1, y1, z1).color(red1, grn2, blu2, alpha).next();
builder.vertex(x2, y1, z1).color(red1, grn2, blu2, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
}
if (ythick)
{
builder.vertex(x1, y1, z1).color(red2, grn1, blu2, alpha).next();
builder.vertex(x1, y2, z1).color(red2, grn1, blu2, alpha).next();
builder.vertex(x2, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
}
if (zthick)
{
builder.vertex(x1, y1, z1).color(red2, grn2, blu1, alpha).next();
builder.vertex(x1, y1, z2).color(red2, grn2, blu1, alpha).next();
builder.vertex(x1, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
}
tessellator.draw();
}
public static void drawBoxFaces(
Tessellator tessellator, BufferBuilder builder,
float x1, float y1, float z1,
float x2, float y2, float z2,
boolean xthick, boolean ythick, boolean zthick,
float red1, float grn1, float blu1, float alpha)
{
builder.begin(GL11.GL_QUADS, VertexFormats.POSITION_COLOR);
if (xthick && ythick)
{
builder.vertex(x1, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z1).color(red1, grn1, blu1, alpha).next();
if (zthick)
{
builder.vertex(x1, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
}
}
if (zthick && ythick)
{
builder.vertex(x1, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y1, z2).color(red1, grn1, blu1, alpha).next();
if (xthick)
{
builder.vertex(x2, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
}
}
// now at least drawing one
if (zthick && xthick)
{
builder.vertex(x1, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y1, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y1, z2).color(red1, grn1, blu1, alpha).next();
if (ythick)
{
builder.vertex(x1, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z1).color(red1, grn1, blu1, alpha).next();
builder.vertex(x2, y2, z2).color(red1, grn1, blu1, alpha).next();
builder.vertex(x1, y2, z2).color(red1, grn1, blu1, alpha).next();
}
}
tessellator.draw();
}
public static void drawCylinderWireframe(Tessellator tessellator, BufferBuilder builder,
float cx, float cy, float cz,
float r, float h, Direction.Axis axis, int subd, boolean isFlat,
float red, float grn, float blu, float alpha)
{
float step = (float)Math.PI / (subd/2);
int num_steps180 = (int)(Math.PI / step)+1;
int num_steps360 = (int)(2*Math.PI / step);
int hsteps = 1;
float hstep = 1.0f;
if (!isFlat)
{
hsteps = (int) Math.ceil(MathHelper.abs(h) / (step * r)) + 1;
hstep = h / (hsteps - 1);
}// draw base
if (axis == Direction.Axis.Y)
{
for (int dh = 0; dh < hsteps; dh++)
{
float hh = dh*hstep;
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = hh;
float z = r * MathHelper.sin(theta);
builder.vertex(x + cx, y + cy, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
if (!isFlat)
{
for (int i = 0; i <= num_steps180; i++)
{
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
float theta = step * i;
float x = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx - x, cy + 0, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy + 0, cz - z).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy + h, cz - z).color(red, grn, blu, alpha).next();
builder.vertex(cx - x, cy + h, cz + z).color(red, grn, blu, alpha).next();
tessellator.draw();
}
}
else
{
builder.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps180; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx - x, cy, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy, cz - z).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
else if (axis == Direction.Axis.X)
{
for (int dh = 0; dh < hsteps; dh++)
{
float hh = dh * hstep;
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float z = r * MathHelper.cos(theta);
float x = hh;
float y = r * MathHelper.sin(theta);
builder.vertex(x + cx, y + cy, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
if (!isFlat)
{
for (int i = 0; i <= num_steps180; i++)
{
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx + 0, cy - y, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx + 0, cy + y, cz - z).color(red, grn, blu, alpha).next();
builder.vertex(cx + h, cy + y, cz - z).color(red, grn, blu, alpha).next();
builder.vertex(cx + h, cy - y, cz + z).color(red, grn, blu, alpha).next();
tessellator.draw();
}
}
else
{
builder.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps180; i++)
{
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx, cy - y, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx, cy + y, cz - z).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
else if (axis == Direction.Axis.Z)
{
for (int dh = 0; dh < hsteps; dh++)
{
float hh = dh * hstep;
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = hh;
float x = r * MathHelper.sin(theta);
builder.vertex(x + cx, y + cy, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
if (!isFlat)
{
for (int i = 0; i <= num_steps180; i++)
{
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = r * MathHelper.sin(theta);
builder.vertex(cx + x, cy - y, cz + 0).color(red, grn, blu, alpha).next();
builder.vertex(cx - x, cy + y, cz + 0).color(red, grn, blu, alpha).next();
builder.vertex(cx - x, cy + y, cz + h).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy - y, cz + h).color(red, grn, blu, alpha).next();
tessellator.draw();
}
}
else
{
builder.begin(GL11.GL_LINES, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps180; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = r * MathHelper.sin(theta);
builder.vertex(cx + x, cy - y, cz).color(red, grn, blu, alpha).next();
builder.vertex(cx - x, cy + y, cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
}
public static void drawCylinderFaces(Tessellator tessellator, BufferBuilder builder,
float cx, float cy, float cz,
float r, float h, Direction.Axis axis, int subd, boolean isFlat,
float red, float grn, float blu, float alpha)
{
float step = (float)Math.PI / (subd/2);
int num_steps180 = (int)(Math.PI / step)+1;
int num_steps360 = (int)(2*Math.PI / step)+1;
if (axis == Direction.Axis.Y)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx, cy, cz).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(x + cx, cy, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
if (!isFlat)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx, cy+h, cz).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(x + cx, cy+h, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
builder.begin(GL11.GL_QUAD_STRIP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx + x, cy + 0, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy + h, cz + z).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
else if (axis == Direction.Axis.X)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx, cy, cz).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx, cy + y, z + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
if (!isFlat)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx+h, cy, cz).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx+h, cy + y, cz + z).color(red, grn, blu, alpha).next();
}
tessellator.draw();
builder.begin(GL11.GL_QUAD_STRIP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float y = r * MathHelper.cos(theta);
float z = r * MathHelper.sin(theta);
builder.vertex(cx + 0, cy + y, cz + z).color(red, grn, blu, alpha).next();
builder.vertex(cx + h, cy + y, cz + z).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
else if (axis == Direction.Axis.Z)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx, cy, cz).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = r * MathHelper.sin(theta);
builder.vertex(x + cx, cy+y, cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
if (!isFlat)
{
builder.begin(GL11.GL_TRIANGLE_FAN, VertexFormats.POSITION_COLOR);
builder.vertex(cx, cy, cz+h).color(red, grn, blu, alpha).next();
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = r * MathHelper.sin(theta);
builder.vertex(x + cx, cy+y, cz+h).color(red, grn, blu, alpha).next();
}
tessellator.draw();
builder.begin(GL11.GL_QUAD_STRIP, VertexFormats.POSITION_COLOR);
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.cos(theta);
float y = r * MathHelper.sin(theta);
builder.vertex(cx + x, cy + y, cz + 0).color(red, grn, blu, alpha).next();
builder.vertex(cx + x, cy + y, cz + h).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
}
public static void drawSphereWireframe(Tessellator tessellator, BufferBuilder builder,
float cx, float cy, float cz,
float r, int subd,
float red, float grn, float blu, float alpha)
{
float step = (float)Math.PI / (subd/2);
int num_steps180 = (int)(Math.PI / step)+1;
int num_steps360 = (int)(2*Math.PI / step);
for (int i = 0; i <= num_steps360; i++)
{
builder.begin(GL11.GL_LINE_STRIP, VertexFormats.POSITION_COLOR);
float theta = step * i ;
for (int j = 0; j <= num_steps180; j++)
{
float phi = step * j ;
float x = r * MathHelper.sin(phi) * MathHelper.cos(theta);
float z = r * MathHelper.sin(phi) * MathHelper.sin(theta);
float y = r * MathHelper.cos(phi);
builder.vertex(x+cx, y+cy, z+cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
for (int j = 0; j <= num_steps180; j++)
{
builder.begin(GL11.GL_LINE_LOOP, VertexFormats.POSITION_COLOR);
float phi = step * j ;
for (int i = 0; i <= num_steps360; i++)
{
float theta = step * i;
float x = r * MathHelper.sin(phi) * MathHelper.cos(theta);
float z = r * MathHelper.sin(phi) * MathHelper.sin(theta);
float y = r * MathHelper.cos(phi);
builder.vertex(x+cx, y+cy, z+cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
public static void drawSphereFaces(Tessellator tessellator, BufferBuilder builder,
float cx, float cy, float cz,
float r, int subd,
float red, float grn, float blu, float alpha)
{
float step = (float)Math.PI / (subd/2);
int num_steps180 = (int)(Math.PI / step)+1;
int num_steps360 = (int)(2*Math.PI / step);
for (int i = 0; i <= num_steps360; i++)
{
float theta = i * step;
float thetaprime = theta+step;
builder.begin(GL11.GL_QUAD_STRIP, VertexFormats.POSITION_COLOR);
for (int j = 0; j <= num_steps180; j++)
{
float phi = j * step;
float x = r * MathHelper.sin(phi) * MathHelper.cos(theta);
float z = r * MathHelper.sin(phi) * MathHelper.sin(theta);
float y = r * MathHelper.cos(phi);
float xp = r * MathHelper.sin(phi) * MathHelper.cos(thetaprime);
float zp = r * MathHelper.sin(phi) * MathHelper.sin(thetaprime);
builder.vertex(x + cx, y + cy, z + cz).color(red, grn, blu, alpha).next();
builder.vertex(xp + cx, y + cy, zp + cz).color(red, grn, blu, alpha).next();
}
tessellator.draw();
}
}
}
@@ -72,172 +72,11 @@ public class BlockValue extends Value
throw new InternalExpressionException("Cannot parse block: "+str);
}
public static VectorLocator locateVec(CarpetContext c, List<LazyValue> params, int offset)
{
return locateVec(c,params, offset, false);
}
public static VectorLocator locateVec(CarpetContext c, List<LazyValue> params, int offset, boolean optionalDirection)
{
try
{
Value v1 = params.get(0 + offset).evalValue(c);
if (v1 instanceof BlockValue)
{
return (new VectorLocator(new Vec3d(((BlockValue) v1).getPos()).add(0.5,0.5,0.5), 1+offset)).fromBlock();
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
Vec3d pos = new Vec3d(
NumericValue.asNumber(args.get(0)).getDouble(),
NumericValue.asNumber(args.get(1)).getDouble(),
NumericValue.asNumber(args.get(2)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
if (args.size()>3 && optionalDirection)
{
yaw = NumericValue.asNumber(args.get(3)).getDouble();
pitch = NumericValue.asNumber(args.get(4)).getDouble();
}
return new VectorLocator(pos,offset+1, yaw, pitch);
}
Vec3d pos = new Vec3d(
NumericValue.asNumber(v1).getDouble(),
NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(),
NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble());
double yaw = 0.0D;
double pitch = 0.0D;
int eatenLength = 3;
if (params.size()>3+offset && optionalDirection)
{
yaw = NumericValue.asNumber(params.get(3 + offset).evalValue(c)).getDouble();
pitch = NumericValue.asNumber(params.get(4 + offset).evalValue(c)).getDouble();
eatenLength = 5;
}
return new VectorLocator(pos,offset+eatenLength, yaw, pitch);
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("Position should be defined either by three coordinates, or a block value");
}
}
public static BlockPos locateBlockPos(CarpetContext c, int xpos, int ypos, int zpos)
{
return new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos);
}
public static LocatorResult fromParams(CarpetContext c, List<LazyValue> params, int offset)
{
return fromParams(c, params,offset, false, false);
}
public static LocatorResult fromParams(CarpetContext c, List<LazyValue> params, int offset, boolean acceptString)
{
return fromParams(c, params,offset, acceptString, false);
}
public static LocatorResult fromParams(CarpetContext c, List<LazyValue> params, int offset, boolean acceptString, boolean optional)
{
try
{
Value v1 = params.get(0 + offset).evalValue(c);
//add conditional from string name
if (optional && v1 instanceof NullValue)
{
return new LocatorResult(null, 1+offset);
}
if (acceptString && v1 instanceof StringValue)
{
return new LocatorResult(fromString(v1.getString()), 1+offset);
}
if (v1 instanceof BlockValue)
{
return new LocatorResult(((BlockValue) v1), 1+offset);
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
int xpos = (int) NumericValue.asNumber(args.get(0)).getLong();
int ypos = (int) NumericValue.asNumber(args.get(1)).getLong();
int zpos = (int) NumericValue.asNumber(args.get(2)).getLong();
return new LocatorResult(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
1+offset);
}
int xpos = (int) NumericValue.asNumber(v1).getLong();
int ypos = (int) NumericValue.asNumber( params.get(1 + offset).evalValue(c)).getLong();
int zpos = (int) NumericValue.asNumber( params.get(2 + offset).evalValue(c)).getLong();
return new LocatorResult(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
3+offset
);
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("Block should be defined either by three coordinates, a block value, or a proper string");
}
}
public static LocatorResult fromParamValues(CarpetContext c, List<Value> params, int offset, boolean acceptString, boolean optional)
{
try
{
Value v1 = params.get(0 + offset);
//add conditional from string name
if (optional && v1 instanceof NullValue)
{
return new LocatorResult(null, 1+offset);
}
if (acceptString && v1 instanceof StringValue)
{
return new LocatorResult(fromString(v1.getString()), 1+offset);
}
if (v1 instanceof BlockValue)
{
return new LocatorResult(((BlockValue) v1), 1+offset);
}
if (v1 instanceof ListValue)
{
List<Value> args = ((ListValue) v1).getItems();
int xpos = (int) NumericValue.asNumber(args.get(0)).getLong();
int ypos = (int) NumericValue.asNumber(args.get(1)).getLong();
int zpos = (int) NumericValue.asNumber(args.get(2)).getLong();
return new LocatorResult(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
1+offset);
}
int xpos = (int) NumericValue.asNumber(v1).getLong();
int ypos = (int) NumericValue.asNumber( params.get(1 + offset)).getLong();
int zpos = (int) NumericValue.asNumber( params.get(2 + offset)).getLong();
return new LocatorResult(
new BlockValue(
null,
c.s.getWorld(),
new BlockPos(c.origin.getX() + xpos, c.origin.getY() + ypos, c.origin.getZ() + zpos)
),
3+offset
);
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("Block should be defined either by three coordinates, a block value, or a proper string");
}
}
public BlockState getBlockState()
{
if (blockState != null)
@@ -342,46 +181,6 @@ public class BlockValue extends Value
public ServerWorld getWorld() { return world;}
public static class LocatorResult
{
public final BlockValue block;
public final int offset;
LocatorResult(BlockValue b, int o)
{
block = b;
offset = o;
}
}
public static class VectorLocator
{
public Vec3d vec;
public final int offset;
public final double yaw;
public final double pitch;
public boolean fromBlock;
VectorLocator(Vec3d v, int o)
{
vec = v;
offset = o;
yaw = 0.0D;
pitch = 0.0D;
}
VectorLocator(Vec3d v, int o, double y, double p)
{
vec = v;
offset = o;
yaw = y;
pitch = p;
}
public VectorLocator fromBlock()
{
fromBlock = true;
return this;
}
}
public enum SpecificDirection {
UP("up",0.5, 0.0, 0.5, Direction.UP),
@@ -1,15 +1,21 @@
package carpet.script.value;
import carpet.CarpetSettings;
import carpet.fakes.EntityInterface;
import carpet.fakes.ItemEntityInterface;
import carpet.fakes.LivingEntityInterface;
import carpet.fakes.MobEntityInterface;
import carpet.fakes.HungerManagerInterface;
import carpet.helpers.Tracer;
import carpet.patches.EntityPlayerMPFake;
import carpet.script.CarpetContext;
import carpet.script.EntityEventsGroup;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.InternalExpressionException;
import carpet.utils.Messenger;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.entity.player.HungerManager;
import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket;
import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
@@ -45,6 +51,7 @@ import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.GameMode;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
@@ -60,6 +67,7 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import static carpet.script.value.NBTSerializableValue.nameFromRegistryId;
import static carpet.utils.MobAI.genericJump;
// TODO: decide whether copy(entity) should duplicate entity in the world.
public class EntityValue extends Value
@@ -118,7 +126,7 @@ public class EntityValue extends Value
@Override
public String getString()
{
return entity.getDisplayName().getString();
return entity.getName().getString();
}
@Override
@@ -220,7 +228,9 @@ public class EntityValue extends Value
put("motion_x", (e, a) -> new NumericValue(e.getVelocity().x));
put("motion_y", (e, a) -> new NumericValue(e.getVelocity().y));
put("motion_z", (e, a) -> new NumericValue(e.getVelocity().z));
put("name", (e, a) -> new StringValue(e.getDisplayName().getString()));
put("name", (e, a) -> new StringValue(e.getName().getString()));
put("display_name", (e, a) -> new StringValue(e.getDisplayName().getString()));
put("command_name", (e, a) -> new StringValue(e.getEntityName()));
put("custom_name", (e, a) -> e.hasCustomName()?new StringValue(e.getCustomName().getString()):Value.NULL);
put("type", (e, a) -> new StringValue(nameFromRegistryId(Registry.ENTITY_TYPE.getId(e.getType()))));
put("is_riding", (e, a) -> new NumericValue(e.hasVehicle()));
@@ -252,7 +262,9 @@ public class EntityValue extends Value
put("despawn_timer", (e, a) -> e instanceof LivingEntity?new NumericValue(((LivingEntity) e).getDespawnCounter()):Value.NULL);
put("item", (e, a) -> (e instanceof ItemEntity)?ListValue.fromItemStack(((ItemEntity) e).getStack()):Value.NULL);
put("count", (e, a) -> (e instanceof ItemEntity)?new NumericValue(((ItemEntity) e).getStack().getCount()):Value.NULL);
put("pickup_delay", (e, a) -> (e instanceof ItemEntity)?new NumericValue(((ItemEntityInterface) e).getPickupDelay()):Value.NULL);
put("pickup_delay", (e, a) -> (e instanceof ItemEntity)?new NumericValue(((ItemEntityInterface) e).getPickupDelayCM()):Value.NULL);
put("portal_cooldown", (e , a) ->new NumericValue(((EntityInterface)e).getPortalTimer()));
put("portal_timer", (e , a) ->new NumericValue(e.netherPortalCooldown));
// ItemEntity -> despawn timer via ssGetAge
put("is_baby", (e, a) -> (e instanceof LivingEntity)?new NumericValue(((LivingEntity) e).isBaby()):Value.NULL);
put("target", (e, a) -> {
@@ -277,13 +289,27 @@ public class EntityValue extends Value
put("sneaking", (e, a) -> e.isSneaking()?Value.TRUE:Value.FALSE);
put("sprinting", (e, a) -> e.isSprinting()?Value.TRUE:Value.FALSE);
put("swimming", (e, a) -> e.isSwimming()?Value.TRUE:Value.FALSE);
/*put("jumping", (e, a) -> {
put("hunger", (e, a) -> {
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getHungerManager().getFoodLevel());
return Value.NULL;
});
put("saturation", (e, a) -> {
if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getHungerManager().getSaturationLevel());
return Value.NULL;
});
put("exhaustion",(e, a)->{
if(e instanceof PlayerEntity) return new NumericValue(((HungerManagerInterface)((PlayerEntity) e).getHungerManager()).getExhaustionCM());
return Value.NULL;
});
put("jumping", (e, a) -> {
if (e instanceof LivingEntity)
{
return ((LivingEntity) e).getJumping()?Value.TRUE:Value.FALSE;
return ((LivingEntityInterface) e).isJumpingCM()?Value.TRUE:Value.FALSE;
}
return Value.NULL;
});*/ //needs mixing
});
put("gamemode", (e, a) -> {
if (e instanceof ServerPlayerEntity)
{
@@ -331,6 +357,8 @@ public class EntityValue extends Value
return Value.NULL;
});
put("team", (e, a) -> e.getScoreboardTeam()==null?Value.NULL:new StringValue(e.getScoreboardTeam().getName()));
put("ping", (e, a) -> {
if (e instanceof ServerPlayerEntity)
{
@@ -370,6 +398,7 @@ public class EntityValue extends Value
StatusEffectInstance pe = ((LivingEntity) e).getStatusEffect(potion);
return ListValue.of( new NumericValue(pe.getAmplifier()), new NumericValue(pe.getDuration()) );
});
put("health", (e, a) ->
{
if (e instanceof LivingEntity)
@@ -475,6 +504,8 @@ public class EntityValue extends Value
return new NBTSerializableValue(nbttagcompound);
return new NBTSerializableValue(nbttagcompound).get(a);
});
put("category",(e,a)->{return new StringValue(e.getType().getCategory().toString().toLowerCase(Locale.ROOT));});
}};
public void set(String what, Value toWhat)
@@ -523,12 +554,40 @@ public class EntityValue extends Value
//((ServerWorld)e.getEntityWorld()).method_14178().sendToNearbyPlayers(e, new EntityVelocityUpdateS2CPacket(e));
}
private static final Map<String, BiConsumer<Entity, Value>> featureModifiers = new HashMap<String, BiConsumer<Entity, Value>>() {{
put("remove", (entity, value) -> entity.remove());
put("age", (e, v) -> e.age = Math.abs((int)NumericValue.asNumber(v).getLong()) );
put("health", (e, v) -> { if (e instanceof LivingEntity) ((LivingEntity) e).setHealth((float) NumericValue.asNumber(v).getDouble()); });
put("health", (e, v) -> {
float health = (float) NumericValue.asNumber(v).getDouble();
if (health <= 0f && e instanceof ServerPlayerEntity)
{
ServerPlayerEntity player = (ServerPlayerEntity) e;
if (player.container != null)
{
// if player dies with open container, then that causes NPE on the client side
// its a client side bug that may never surface unless vanilla gets into scripting at some point
// bug: #228
player.closeContainer();
}
((LivingEntity) e).setHealth(health);
}
if (e instanceof LivingEntity) ((LivingEntity) e).setHealth(health);
});
// todo add handling of the source for extra effects
/*put("damage", (e, v) -> {
float dmgPoints;
DamageSource source;
if (v instanceof ListValue && ((ListValue) v).getItems().size() > 1)
{
List<Value> vals = ((ListValue) v).getItems();
dmgPoints = (float) NumericValue.asNumber(v).getDouble();
source = DamageSource ... yeah...
}
else
{
}
});*/
put("kill", (e, v) -> e.kill());
put("location", (e, v) ->
{
@@ -751,26 +810,12 @@ public class EntityValue extends Value
else if (v instanceof ListValue)
{
List<Value> lv = ((ListValue) v).getItems();
if (lv.get(0) instanceof BlockValue)
Vector3Argument locator = Vector3Argument.findIn(lv, 0, false);
pos = new BlockPos(locator.vec.x, locator.vec.y, locator.vec.z);
if (lv.size() > locator.offset)
{
pos = ((BlockValue) lv.get(0)).getPos();
if (lv.size()>1)
{
distance = (int) NumericValue.asNumber(lv.get(1)).getLong();
}
distance = (int) NumericValue.asNumber(lv.get(locator.offset)).getLong();
}
else if (lv.size()>=3)
{
pos = new BlockPos(NumericValue.asNumber(lv.get(0)).getLong(),
NumericValue.asNumber(lv.get(1)).getLong(),
NumericValue.asNumber(lv.get(2)).getLong());
if (lv.size()>3)
{
distance = (int) NumericValue.asNumber(lv.get(4)).getLong();
}
}
else throw new InternalExpressionException("'home' requires at least one position argument, and optional distance");
}
else throw new InternalExpressionException("'home' requires at least one position argument, and optional distance");
@@ -800,6 +845,20 @@ public class EntityValue extends Value
}
});
put("portal_cooldown", (e , v) ->
{
if (v==null)
throw new InternalExpressionException("'portal_cooldown' requires a value to set");
e.netherPortalCooldown = NumericValue.asNumber(v).getInt();
});
put("portal_timer", (e , v) ->
{
if (v==null)
throw new InternalExpressionException("'portal_timer' requires a value to set");
((EntityInterface) e).setPortalTimer(NumericValue.asNumber(v).getInt());
});
put("ai", (e, v) ->
{
if (e instanceof MobEntity)
@@ -820,7 +879,10 @@ public class EntityValue extends Value
if (!(e instanceof LivingEntity)) return;
LivingEntity le = (LivingEntity)e;
if (v == null)
{
le.clearStatusEffects();
return;
}
else if (v instanceof ListValue)
{
List<Value> lv = ((ListValue) v).getItems();
@@ -840,7 +902,7 @@ public class EntityValue extends Value
{
le.removeStatusEffect(effect);
return;
}
}
int amplifier = 0;
if (lv.size() > 2)
amplifier = (int)NumericValue.asNumber(lv.get(2)).getLong();
@@ -854,24 +916,81 @@ public class EntityValue extends Value
return;
}
}
else
{
String effectName = v.getString();
StatusEffect effect = Registry.STATUS_EFFECT.get(new Identifier(effectName));
if (effect == null)
throw new InternalExpressionException("Wrong effect name: "+effectName);
le.removeStatusEffect(effect);
return;
}
throw new InternalExpressionException("'effect' needs either no arguments (clear) or effect name, duration, and optional amplifier, show particles and show icon");
});
// gamemode
// spectate
// "fire"
// "extinguish"
// "silent"
// "gravity"
// "invulnerable"
// "dimension"
// "item"
// "count",
// "age",
// "effect_"name
// "hold"
// "hold_offhand"
// "jump"
put("gamemode", (e,v)->{
if(!(e instanceof ServerPlayerEntity)) return;
GameMode toSet = v instanceof NumericValue ?
GameMode.byId(((NumericValue) v).getInt(), null) :
GameMode.byName(v.getString().toLowerCase(Locale.ROOT), null);
if (toSet != null) ((ServerPlayerEntity) e).setGameMode(toSet);
});
put("jumping",(e,v)->{
if(!(e instanceof LivingEntity)) return;
((LivingEntity) e).setJumping(v.getBoolean());
});
put("jump",(e,v)->{
if (e instanceof LivingEntity)
{
((LivingEntityInterface)e).doJumpCM();
}
else
{
genericJump(e);
}
});
put("silent",(e,v)-> e.setSilent(v.getBoolean()));
put("gravity",(e,v)-> e.setNoGravity(!v.getBoolean()));
put("invulnerable",(e,v)-> e.setInvulnerable(v.getBoolean()));
put("fire",(e,v)-> e.setFireTicks((int)NumericValue.asNumber(v).getLong()));
put("hunger", (e, v)-> {
if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setFoodLevel((int) NumericValue.asNumber(v).getLong());
});
put("exhaustion", (e, v)-> {
if(e instanceof PlayerEntity) ((HungerManagerInterface) ((PlayerEntity) e).getHungerManager()).setExhaustionCM(NumericValue.asNumber(v).getFloat());
});
put("add_exhaustion", (e, v)-> {
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());
});
// gamemode [check]
// spectate [check]
// "fire" [check]
// "extinguish" [set fire ticks to 0]
// "silent" [check]
// "gravity" [check]
// "invulnerable" [check]
// "dimension" []
// "item" []
// "count", []
// "age", [check]
// "effect_"name []
// "hold" [inventory_set?]
// "hold_offhand" [inventory_set?]
// "jump" [check]
// "nbt" <-big one, for now use run('data merge entity ...
}};
@@ -0,0 +1,40 @@
package carpet.script.value;
import net.minecraft.text.BaseText;
public class FormattedTextValue extends StringValue
{
BaseText text;
public FormattedTextValue(BaseText text)
{
super(null);
this.text = text;
}
@Override
public String getString() {
return text.getString();
}
@Override
public boolean getBoolean() {
return text.getSiblings().size() > 0;
}
@Override
public Value clone()
{
return new FormattedTextValue(text);
}
@Override
public String getTypeString()
{
return "text";
}
public BaseText getText()
{
return text;
}
}
@@ -5,6 +5,7 @@ import carpet.script.Expression;
import carpet.script.Fluff;
import carpet.script.LazyValue;
import carpet.script.Tokenizer;
import carpet.script.bundled.Module;
import carpet.script.exception.BreakStatement;
import carpet.script.exception.ContinueStatement;
import carpet.script.exception.ExitStatement;
@@ -58,6 +59,8 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction
return name;
}
public Module getModule() {return expression.module;}
@Override
public String getPrettyString()
{
@@ -48,7 +48,7 @@ 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, "[%d, %d, ..., %d)",start, start+stepp, limit);
}
};
}
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more