diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 6e63fd9f..eed59c12 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -296,8 +296,8 @@ For more information, see: [EvalEx GitHub repository](https://github.com/uklimas `scarpet` provides a number of constants that can be used literally in scripts * `null`: nothing, zilch, not even false -* `true`: pure true, or just 1 (one) -* `false`: false truth, or true falsth, 0 (zero) actually +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` * `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 * `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 @@ -343,7 +343,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct 1357-5 => 1352 1357-'5' => 137 3*'foo'-'o' => 'fff' -l(1,3,5)+7 => l(8,10,12) +[1,3,5]+7 => [8,10,12] As you can see, values can behave differently when mixed with other types in the same expression. @@ -403,8 +403,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea any extra arguments.
-l(1,2,3) ~ 2 => 1 -l(1,2,3) ~ 4 => null +[1,2,3] ~ 2 => 1 +[1,2,3] ~ 4 => null 'foobar' ~ 'baz' => null 'foobar' ~ '.b' => 'ob' @@ -430,10 +430,10 @@ Or an example to find if a player has specific enchantment on a held axe (eitherglobal_get_enchantment(p, ench) -> ( -$ for(l('main','offhand'), +$ for(['main','offhand'], $ holds = query(p, 'holds', _); $ if( holds, -$ l(what, count, nbt) = holds; +$ [what, count, nbt] = holds; $ if( what ~ '_axe' && nbt ~ ench, $ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') ) $ ) @@ -470,8 +470,8 @@ Examples: 'foo'*3 => 'foofoofoo' 'foofoofoo' / 3 => 'foo' 'foofoofoo'-'o' => 'fff' -l(1,2,3)+1 => l(2,3,4) -b = l(100,63,100); b+l(10,0,10) => l(110,63,110) +[1,2,3]+1 => [2,3,4] +b = [100,63,100]; b+[10,0,10] => [110,63,110] {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}@@ -513,11 +513,12 @@ second operand is not necessary, it won't be evaluated, which means one can use case of success returns first positive operand (`||`) or last one (`&&`).-true || false => 1 -null || false => 0 -null != false || run('kill gnembon') => 1 // gnembon survives -null != false && run('kill gnembon') => 0 // when cheats not allowed -null != false && run('kill gnembon') => 1 // gnembon dies, cheats allowed +true || false => true +null || false => false +false || null => null +null != false || run('kill gnembon') // gnembon survives +null != false && run('kill gnembon') // when cheats not allowed +null != false && run('kill gnembon') // gnembon dies, cheats allowed### `Assignment Operators = <> +=` @@ -531,11 +532,11 @@ require rewriting of the array anyways.a = 5 => a == 5 -l(a,b,c) = l(3,4,5) => a==3, b==4, c==5 -l(minx,maxx) = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj) -l(a,b,c,d,e,f) = l(range(6)); l(a,b,c) <> l(d,e,f); l(a,b,c,d,e,f) => [3,4,5,0,1,2] -a = l(1,2,3); a += 4 => [1,2,3,4] -a = l(1,2,3,4); a = filter(a,_!=2) => [1,3,4] +[a,b,c] = [3,4,5] => a==3, b==4, c==5 +[minx,maxx] = sort(xi,xj); // minx assumes min(xi, xj) and maxx, max(xi, xj) +[a,b,c,d,e,f] = [range(6)]; [a,b,c] <> [d,e,f]; [a,b,c,d,e,f] => [3,4,5,0,1,2] +a = [1,2,3]; a += 4 => [1,2,3,4] +a = [1,2,3,4]; a = filter(a,_!=2) => [1,3,4]### `Unary Operators - +` @@ -553,12 +554,12 @@ Require a number, flips the sign. One way to assert it's a number is by crashing flips boolean condition of the expression. Equivalent of `bool(expr)==false`-!true => 0 -!false => 1 -!null => 1 -!5 => 0 -!l() => 1 -!l(null) => 0 +!true => false +!false => true +!null => true +!5 => false +!l() => true +!l(null) => false# Arithmetic operations @@ -696,30 +697,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` -Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is -alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other places where boolean context can be used. This can be used in places where API functions return string values to -represent binary values +represent binary values.-bool(pi) => 1 -bool(false) => 0 -bool('') => 0 -bool(l()) => 0 -bool(l('')) => 1 -bool('foo') => 1 -bool('false') => 0 -bool('nulL') => 0 -if('false',1,0) => 1 +bool(pi) => true +bool(false) => false +bool('') => false +bool(l()) => false +bool(l('')) => true +bool('foo') => true +bool('false') => false +bool('nulL') => false +if('false',1,0) => true### `number(expr)` -Returns a numeric context of the expression. Can be used to read numbers from strings +Returns a numeric context of the expression. Can be used to read numbers from strings, or other types-number(null) => null +number(null) => 0 number(false) => 0 +number(true) => 1 number('') => null number('3.14') => 3.14 number(l()) => 0 @@ -747,15 +749,15 @@ Supported types (with `"%>"` syntax): * `%%`: '%' character-str(null) => null -str(false) => 0 -str('') => null -str('3.14') => 3.14 -str(l()) => 0 -str(l('')) => 1 -str('foo') => null -str('3bar') => null -str(2)+str(2) => 22 +str(null) => 'null' +str(false) => 'false' +str('') => '' +str('3.14') => '3.14' +str([]) => '[]' +str(['']) => '[]' +str('foo') => 'foo' +str('3bar') => '3bar' +str(2)+str(2) => '22' str('pi: %.2f',pi) => 'pi: 3.14' str('player at: %d %d %d',pos(player())) => 'player at: 567, -2423, 124'@@ -916,7 +918,7 @@ same random object. Scarpet keeps track of up to 1024 custom random number genermap(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8] -map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0] +map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false] map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]@@ -1217,12 +1219,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound and `continue` have no sense and cannot be used inside of `expr` body.-all([1,2,3], check_prime(_)) => 1 -all(neighbours(x,y,z), _=='stone') => 1 // if all neighbours of [x, y, z] are stone -map(filter(rect(0,4,0,1000,0,1000), l(x,y,z)=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) ) +all([1,2,3], check_prime(_)) => true +all(neighbours(x,y,z), _=='stone') => true // if all neighbours of [x, y, z] are stone +map(filter(rect(0,4,0,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') ), pos(_) ) => [[-298, 4, -703], [-287, 4, -156], [-269, 4, 104], [242, 4, 250], [-159, 4, 335], [-208, 4, 416], [-510, 4, 546], [376, 4, 806]] // find all 3x3 bedrock structures in the top bedrock layer -map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), l(x,y,z)=pos(_); +map( filter( rect(0,4,0,1000,1,1000,1000,0,1000), [x,y,z]=pos(_); all(rect(x,y,z,1,0,1),_=='bedrock') && for(rect(x,y-1,z,1,1,1,1,0,1),_=='bedrock')<8), pos(_) ) => [[343, 3, -642], [153, 3, -285], [674, 3, 167], [-710, 3, 398]] // ditto, but requiring at most 7 bedrock block in the 18 blocks below them @@ -1475,15 +1477,73 @@ returns everywhere, but it would often lead to a messy code. It terminates entire program passing `expr` as the result of the program execution, or null if omitted. -### `try(expr, catch_expr(_)?) ... throw(value?)` +### `try(expr)` `try(expr, user_catch_expr)` `try(expr, type, catch_expr, type?, catch_expr?, ...)` -`try` function evaluates expression, and continues further unless `throw` function is called anywhere -inside `expr`. In that case the `catch_expr` is evaluates with `_` set to the argument `throw` was called with. -This mechanic accepts skipping thrown value - it throws null instead, and catch expression - then try returns -null as well This mechanism allows to terminate large portion of a convoluted call stack and continue program -execution. There is only one level of exceptions currently in carpet, so if the inner function also defines -the `try` catchment area, it will received the exception first, but it can technically rethrow the value its -getting for the outer scope. Unhandled throw acts like an exit statement. +`try` evaluates expression, allowing capturing exceptions that would be thrown inside `expr` statement. The exceptions can be +thrown explicitly using `throw()` or internally by scarpet where code is correct but detects illegal state. The 2-argument form +catches only user-thrown exceptions and one argument call `try(expr)` is equivalent to `try(expr, null)`, +or `try(expr, 'user_exception', null)`. If multiple `type-catch` pairs are defined, the execution terminates on the first +applicable type for the exception thrown. Therefore, even if the caught exception matches multiple filters, only +the first matching block will be executed. + +Catch expressions are evaluated with +`_` set to the value associated with the exception and `_trace` set to contain details about point of error (token, and line and +column positions), call stack and local +variables at the time of failure. The `type` will catch any exception of that type and any subtype of this type. + + +You can use `try` mechanism to exit from large portion of a convoluted call stack and continue program execution, although catching +exceptions is typically much more expensive comparing to not throwing them. + +The `try` function allows you to catch some scarpet exceptions for cases covering invalid data, like invalid +blocks, biomes, dimensions and other things, that may have been modified by datapacks, resourcepacks or other mods, +or when an error is outside of the programmers scope, such as problems when reading or decoding files. + +This is the hierarchy of the exceptions that could be thrown/caught in the with the `try` function: +- `exception`: This is the base exception. Catching `'exception'` allows to catch everything that can be caught, +but like everywhere else, doing that sounds like a bad idea. + - `value_exception`: This is the parent for any exception that occurs due to an + incorrect argument value provided to a built-in function + - `unknown_item`, `unknown_block`, `unknown_biome`, `unknown_sound`, `unknown_particle`, + `unknown_poi_type`, `unknown_dimension`, `unknown_structure`, `unknown_criterion`: Specific + errors thrown when a specified internal name does not exist or is invalid. + - `io_exception`: This is the parent for any exception that occurs due to an error handling external data. + - `nbt_read_error`: Incorrect input NBT file. + - `json_read_error`: Incorrect input JSON data. + - `user_exception`: Exception thrown by default with `throw` function. + +Synopsis: ++inner_call() -> +( + aaa = 'booyah'; + try( + for (range(10), item_tags('stick'+_*'k')); + , + print(_trace) // not caught, only catching user_exceptions + ) +); + +outer_call() -> +( + try( + inner_call() + , 'exception', // catching everything + print(_trace) + ) +); ++Producing: +``` +{stack: [[, inner_call, 1, 14]], locals: {_a: 0, aaa: booyah, _: 1, _y: 0, _i: 1, _x: 0, _z: 0}, token: [item_tags, 5, 23]} +``` + +### `throw(value?)`, `throw(type, value)`, `throw(subtype, type, value)` + +Throws an exception that can be caught in a `try` block (see above). If ran without arguments, it will throw a `user_exception` +passing `null` as the value to the `catch_expr`. With two arguments you can mimic any other exception type thrown in scarpet. +With 3 arguments, you can specify a custom exception acting as a `subtype` of a provided `type`, allowing to customize `try` +statements with custom exceptions. ### `if(cond, expr, cond?, expr?, ..., default?)` @@ -1518,12 +1578,12 @@ In case to simplify the access with nested objects, you can add chain of address than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`. -get(l(range(10)), 5) => 5 -get(l(range(10)), -1) => 9 -get(l(range(10)), 10) => 0 -l(range(10)):93 => 3 +get([range(10)], 5) => 5 +get([range(10)], -1) => 9 +get([range(10)], 10) => 0 +[range(10)]:93 => 3 get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead -get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar') => 3 +get({ 'foo' -> 2, 'bar' -> 3, 'baz' -> 4 }, 'bar') => 3### `has(container, address, ...), has(lvalue)` @@ -1595,13 +1655,13 @@ of the existing elements. Use `replace` to remove and replace existing element. and merges keys from `value` with the compound tag under the path-a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3] -a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4] -a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3] -a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6] -a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3] -a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]] -a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null} +a = [1, 2, 3]; put(a, 1, 4); a => [1, 4, 3] +a = [1, 2, 3]; put(a, null, 4); a => [1, 2, 3, 4] +a = [1, 2, 3]; put(a, 1, 4, 'insert'); a => [1, 4, 2, 3] +a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a => [1, 2, 3, 4, 5, 6] +a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a => [1, 4, 5, 6, 2, 3] +a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]] +a = {1,2,3,4}; put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null} tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}} tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]} tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag @@ -1682,10 +1742,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t cannot go back nor track where they are in the sequence (see examples).-slice([0,1,2,3,4,5], 1, 3) => [1, 2, 3] +slice([0,1,2,3,4,5], 1, 3) => [1, 2] slice('foobar', 0, 1) => 'f' slice('foobar', 3) => 'bar' -slice(range(10), 3, 5) => [3, 4, 5] +slice(range(10), 3, 5) => [3, 4] slice(range(10), 5) => [5, 6, 7, 8, 9] r = range(100); [slice(r, 5, 7), slice(r, 1, 3)] => [[5, 6], [8, 9]]@@ -1720,7 +1780,7 @@ Primarily to be used in higher order functionsrange(10) => [...] -l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[range(10)] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] reduce(range(10),_a+_, 0) => 45 range(5,10) => [5, 6, 7, 8, 9] @@ -2077,6 +2137,8 @@ when it was used. Block values passed in various places like `scan` functions, e its properties are needed. This means that if the block at the location changes before its queried in the program this might result in getting the later state, which might not be desired. Consider the following example: +Throws `unknown_block` if provided input is not valid. +set(10,10,10,'stone'); scan(10,10,10,0,0,0, b = _); set(10,10,10,'air'); @@ -2106,6 +2168,8 @@ properties that the original source block may have contained. The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed +Throws `unknown_block` if provided block to set is not valid +set(0,5,0,'bedrock') => bedrock set(l(0,5,0), 'bedrock') => bedrock @@ -2184,6 +2248,8 @@ but doesn't open chests / containers, so have no effect on interactive blocks, l Returns true if placement/use was successful, false otherwise. +Throws `unknown_item` if `item` doesn't exist +place_item('stone',x,y,z) // places a stone block on x,y,z block place_item('piston,x,y,z,'down') // places a piston facing down @@ -2202,6 +2268,8 @@ If type is `null`, POI at position is removed. In any case, previous POI is also Interestingly, `unemployed`, and `nitwit` are not used in the game, meaning, they could be used as permanent spatial markers for scarpet apps. `meeting` is the only one with increased max occupancy of 32. +Throws `unknown_poi` if the provided point of interest doesn't exist + ### `set_biome(pos, biome_name, update=true)` Changes the biome at that block position. if update is specified and false, then chunk will not be refreshed @@ -2212,6 +2280,8 @@ is only effective if you set it at y=0, and affects the entire column of. In the specific Y coordinate of the biome you want to change, and it affects roughly 4x4x4 area (give or take some random noise). +Throws `unknown_biome` if the `biome_name` doesn't exist. + ### `update(pos)` Causes a block update at position. @@ -2236,6 +2306,8 @@ In item context, `true` means that breaking item has no nbt to use, `null` indic considered broken in process, and `nbt` type value, for a resulting NBT tag on a hypothetical tool. Its up to the programmer to use that nbt to apply it where it belong +Throws `unknown_item` if `tool` doesn't exist. + Here is a sample code that can be used to mine blocks using items in player inventory, without using player context for mining. Obviously, in this case the use of `harvest` would be much more applicable: @@ -2327,6 +2399,8 @@ back in state definition in various applications where block properties are requ `block_state` can also accept block names as input, returning block's default state. +Throws `unknown_block` if the provided input is not valid. +set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z) => {waterlogged: false, half: top, open: false, ...} set(x,y,z,'iron_trapdoor','half','top'); block_state(x,y,z,'half') => top @@ -2347,6 +2421,8 @@ Without arguments, returns list of available tags, with block supplied (either b of tags the block belongs to, and if a tag is specified, returns `null` if tag is invalid, `false` if this block doesn't belong to this tag, and `true` if the block belongs to the tag. +Throws `unknown_block` if `block` doesn't exist + ### `block_data(pos)` Return NBT string associated with specific location, or null if the block does not carry block data. Can be currently @@ -2381,9 +2457,9 @@ poi(x,y,z,5) => [['nether_portal',0,[7,8,9]],['nether_portal',0,[7,9,9]]] // two Without arguments, returns the list of biomes in the world. -With block, or name, returns the name of the biome in that position, or null, if provided biome is not valid. +With block, or name, returns the name of the biome in that position, or throws `'unknown_biome'` if provided biome or block are not valid. -With an optional feature, it returns value for the specified attribute for that biome. Available and querable features include: +With an optional feature, it returns value for the specified attribute for that biome. Available and queryable features include: * `'top_material'`: unlocalized block representing the top surface material * `'under_material'`: unlocalized block representing what sits below topsoil * `'category'`: the parent biome this biome is derived from. Possible values include: @@ -2772,6 +2848,7 @@ If structure is not specified, it will return a set of structure names that are as keys, and same type of map values as with a single structure call. An empty set or an empty map would indicate that nothing should be generated there. +Throws `unknown_structure` if structure doesn't exist. ### `structures(pos), structures(pos, structure_name)` @@ -2805,6 +2882,8 @@ structure starts. Requires a `Structure Variant` or `Standard Structure` name (see above). If standard name is used, the variant of the structure may depend on the biome, otherwise the default structure for this type will be generated. +Throws `unknown_structure` if structure doesn't exist. + ### `plop(pos, what)` Plops a structure or a feature at a given `pos`, so block, triple position coordinates or a list of coordinates. @@ -2827,7 +2906,9 @@ All generated structures will retain their properties, like mob spawning, howeve itself has certain rules to spawn mobs, like plopping a nether fortress in the overworld will not spawn nether mobs, because nether mobs can spawn only in the nether, but plopped in the nether - will behave like a valid nether fortress. -### `custom_dimension(name, seed?)` +### (deprecated) `custom_dimension(name, seed?)` + +Deprecated by `create_datapack()` which can be used to setup custom dimensions Ensures the dimension with the given `'name'` is available and configured with the given seed. It merely sets the world generator settings to the overworld, and the optional custom seed (or using current world seed, if not provided). @@ -3082,7 +3163,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t ### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')` -String of entity name +String of entity name or formatted text in the case of `display_name`query(e,'name') => Leatherworker @@ -3808,14 +3889,16 @@ an inventory, all API functions typically do nothing and return null. Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item. -### item_list(tag?) +### `item_list(tag?)` With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid. -### item_tags(item, tag?) +### `item_tags(item, tag?)` Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag +Throws `unknown_item` if item doesn't exist. + ### `stack_limit(item)` Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), @@ -3823,6 +3906,8 @@ or 64 - rest. It is recommended to consult this, as other inventory API function it is up to the programmer to keep it at bay. As of 1.13, game checks for negative numbers and setting an item to negative is the same as empty. +Throws `unknown_item` if item doesn't exist. +stack_limit('wooden_axe') => 1 stack_limit('ender_pearl') => 16 @@ -3833,6 +3918,8 @@ stack_limit('stone') => 64 Returns the string representing the category of a given item, like `building_blocks`, `combat`, or `tools`. +Throws `unknown_item` if item doesn't exist. +item_category('wooden_axe') => tools item_category('ender_pearl') => misc @@ -3955,6 +4042,8 @@ while( (slot = inventory_find(p, 'diamond', slot)) != null, 41, drop_item(p, slo inventory_drop(x,y,z, 0) => 64 // removed and spawned in the world a full stack of items+Throws `unknown_item` if item doesn't exist. + ### `inventory_remove(inventory, item, amount?)` Removes amount (defaults to 1) of item from inventory. If the inventory doesn't have the defined amount, nothing @@ -4357,6 +4446,8 @@ Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'d If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. If the objective was added, returns `true`. If nothing is affected, returns `null` +Throws `unknown_criterion` if criterion doesn't exist. +scoreboard_add('counter') scoreboard_add('lvl','level') @@ -4519,6 +4610,8 @@ optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1 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. + +Throws `unknown_sound` if sound doesn't exist. ## Particles @@ -4529,12 +4622,15 @@ of 0, and to all players nearby, but these options can be changed via optional a 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'`. +Throws `unknown_particle` if particle doesn't exist. + ### `particle_line(name, pos, pos2, density?, player?)` Renders a line of particles from point `pos` to `pos2` with supplied density (defaults to 1), which indicates how far apart you would want particles to appear, so `0.1` means one every 10cm. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. ### `particle_box(name, pos, pos2, density?, player?)` ### `particle_rect` (deprecated) @@ -4542,6 +4638,8 @@ that player will receive particles. Renders a cuboid of particles between points `pos` and `pos2` with supplied density. If a player (or player name) is supplied, only that player will receive particles. +Throws `unknown_particle` if particle doesn't exist. + ## Markers ### `draw_shape(shape, duration, key?, value?, ... )`, @@ -4828,6 +4926,10 @@ record to the file. Since files are closed after each write, sending multiple li write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the third argument. +Throws: +- `nbt_read_error`: When failed to read NBT file. +- `json_read_error`: When failed to read JSON file. The exception data will contain details about the problem. +write_file('foo', 'shared_text, ['one', 'two']); write_file('foo', 'shared_text', 'three\n', 'four\n'); @@ -4879,6 +4981,8 @@ specific data directory is under `world/scripts/foo.data/bar/../baz.nbt`, and sh You can use app data to save non-vanilla information separately from the world and other scripts. +Throws `nbt_read_error` if failed to read app data. + ### `store_app_data(tag)` Note: `store_app_data(tag, file)` and `store_app_data(tag, file, shared?)` usages deprecated. Use `write_file` instead. @@ -4893,6 +4997,83 @@ Returns `true` if the file was saved successfully, `false` otherwise. Uses the same file structure for exclusive app data, and shared data folder as `load_app_data`. +### `create_datapack(name, data)` + +Creates and loads custom datapack. The data has to be a map representing the file structure and the content of the +json files of the target pack. + +Returns `null` if the pack with this name already exists or is loaded, meaning no change has been made. +Returns `false` if adding of the datapack wasn't successful. +Returns `true` if creation and loading of the datapack was successful. Loading of a datapack results in +reloading of all other datapacks (vanilla restrictions, identical to /datapack enable), however unlike with `/reload` +command, scarpet apps will not be reloaded by adding a datapack using `create_datapack`. + +Currently, only json files are supported in the packs. `'pack.mcmeta'` file is added automatically. + +Reloading of datapacks that define new dimensions is not implemented in vanilla. Vanilla game only loads +dimension information on server start. `create_datapack` is therefore a direct replacement of manually ploping of the specified +file structure in a datapack file and calling `/datapack enable` on the new datapack with all its quirks and sideeffects +(like no worldgen changes, reloading all other datapacks, etc.). To enable newly added custom dimensions, call much more +experimental `check_hidden_dimensions()` after adding a datapack if needed. + +Synopsis: ++## `reset_seed(seed)` + +Resets the sequence of the randomizer used by `rand` for this seed to its initial state. Returns a boolean value +indicating if the given seed has been used or not. + ### `perlin(x), perlin(x, y), perlin(x, y, z), perlin(x, y, z, seed)` returns a noise value from `0.0` to `1.0` (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples diff --git a/docs/scarpet/resources/editors/idea/1.txt b/docs/scarpet/resources/editors/idea/1.txt index d69ced42..e83e03da 100644 --- a/docs/scarpet/resources/editors/idea/1.txt +++ b/docs/scarpet/resources/editors/idea/1.txt @@ -63,6 +63,7 @@ reduce relu replace replace_first +reset_seed return round sec diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 1c3251b3..42a7bf9e 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -97,6 +97,7 @@ scoreboard scoreboard_add scoreboard_remove scoreboard_display +scoreboard_property tag_matches team_add team_leave diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index b44bb643..b10e15e4 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -25,7 +25,7 @@+script run create_datapack('foo', +{ + 'foo' -> { + 'bar.json' -> { + 'c' -> true, + 'd' -> false, + 'e' -> {'foo' -> [1,2,3]}, + 'a' -> 'foobar', + 'b' -> 5 + } + } +}) + +script run create_datapack('funky_world', { + 'data' -> { + 'minecraft' -> { + 'dimension' -> { + 'custom_ow.json' -> { + 'type' -> 'minecraft:the_end', + 'generator' -> { + 'biome_source' -> { + 'seed' -> 0, + 'large_biomes' -> false, + 'type' -> 'minecraft:vanilla_layered' + }, + 'seed' -> 0, + 'settings' -> 'minecraft:nether', + 'type' -> 'minecraft:noise' + } + } + } + } + } +}); +check_hidden_dimensions(); => ['funky_world'] + ++ +### `enable_hidden_dimensions()` + +The function reads current datapack settings detecting new dimensions defined by these datapacks that have not yet been added +to the list of current dimensions and adds them so that they can be used and accessed right away. It doesn't matter how the +datapacks have been added to the game, either with 'create_datapack()' or manually by dropping a datapack file and calling +`/datapack enable` on it. Returns the list of valid dimension names / identifiers that has been added in the process. + +Fine print: The function should be +considered experimental. There 'should not be' (famous last words) any side-effects if no worlds are added. Already connected +clients will not see suggestions for commands that use dimensions `/execute in` (vanilla client limitation) +but all commands should work just fine with +the new dimensions. Existing worlds that have gotten modified settings by the datapacks will not be reloaded or replaced. +The usability of the dimensions added this way has not been fully tested, but it seems it works just fine. Generator settings +for the new dimensions will not be added to `'level.dat'` but it will be added there automatically next time the game restarts by +vanilla. One could have said to use this method with caution, and the authors take no responsibility of any losses incurred due to +mis-handlilng of the temporary added dimensions, yet the feature itself (custom dimensions) is clearly experimental for Mojang +themselves, so that's about it. + ### `tick_time()` Returns server tick counter. Can be used to run certain operations every n-th ticks, or to count in-game time. @@ -4956,6 +5137,8 @@ Evaluates the expression `expr` with different dimension execution context. `smt world-localized block, so not `block('stone')`, or a string representing a dimension like: `'nether'`, `'the_nether'`, `'end'` or `'overworld'`, etc. +Throws `unknown_dimension` if provided dimension can't be found. + ### `view_distance()` Returns the view distance of the server. @@ -5010,6 +5193,7 @@ Available options in the scarpet app space: Relevant world related properties * `world_name` - name of the world * `world_seed` - a numeric seed of the world + * `world_dimensions` - a list of dimensions in the world * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 1a7d3c33..78f61be4 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -4,7 +4,7 @@ Collection of other methods that control smaller, yet still important aspects of ## Sounds -### `sound(name, pos, volume?, pitch?, mixer?)` +### `sound()`, `sound(name, pos, volume?, pitch?, mixer?)` Plays a specific sound `name`, at block or position `pos`, with optional `volume` and modified `pitch`, and under optional `mixer`. Default values for `volume`, `pitch` and `mixer` are `1.0`, `1.0`, and `master`. @@ -12,17 +12,23 @@ Valid mixer options are `master`, `music`, `record`, `weather`, `block`, `hostil and `voice`. `pos` can be either a block, triple of coords, or a list of thee numbers. Uses the same options as a corresponding `playsound` command. +Used with no arguments, return the list of available sound names. + Throws `unknown_sound` if sound doesn't exist. ## Particles -### `particle(name, pos, count?. spread?, speed?, player?)` +### `particle()`, `particle(name, pos, count?. spread?, speed?, player?)` Renders a cloud of particles `name` centered around `pos` position, by default `count` 10 of them, default `speed` of 0, and to all players nearby, but these options can be changed via optional arguments. Follow vanilla `/particle` command on details on those options. Valid particle names are for example `'angry_villager', 'item diamond', 'block stone', 'dust 0.8 0.1 0.1 4'`. +Used with no arguments, return the list of available particle names. Note that some of the names do not correspond to a valid +particle that can be fed to `particle(...)` function due to a fact that some particles need more configuration +to be valid, like `dust`, `block` etc. Should be used as a reference only. + Throws `unknown_particle` if particle doesn't exist. ### `particle_line(name, pos, pos2, density?, player?)` diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 540bc9d8..10221a87 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -315,13 +315,13 @@ used to match specific information from it, or use it to copy to another block block_data(x,y,z) => '{TransferCooldown:0,x:450,y:68, ... }'-### `poi(pos), poi(pos, radius?, type?, status?, column?)` +### `poi(pos), poi(pos, radius?, type?, status?, column_search?)` Queries a POI (Point of Interest) at a given position, returning `null` if none is found, or tuple of poi type and its occupancy load. With optional `type`, `radius` and `status`, returns a list of POIs around `pos` within a given `radius`. If the `type` is specified, returns only poi types of that types, or everything if omitted or `'any'`. If `status` is specified (either `'any'`, `'available'`, or `'occupied'`) returns only POIs with that status. -With `column` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire +With `column_search` set to `true`, it will return all POIs in a cuboid with `radius` blocks away on x and z, in the entire block column from 0 to 255. Default (`false`) returns POIs within a spherical area centered on `pos` and with `radius` radius. diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md index 9d152098..881df496 100644 --- a/docs/scarpet/api/Scoreboard.md +++ b/docs/scarpet/api/Scoreboard.md @@ -6,13 +6,12 @@ Displays or modifies individual scoreboard values. With no arguments, returns th With specified `objective`, lists all keys (players) associated with current objective, or `null` if objective does not exist. With specified `objective` and `key`, returns current value of the objective for a given player (key). With additional `value` sets a new scoreboard - value, returning previous value associated with the `key`. + value, returning previous value associated with the `key`. If the `value` is null, resets the scoreboard value. ### `scoreboard_add(objective, criterion?)` Adds a new objective to scoreboard. If `criterion` is not specified, assumes `'dummy'`. -If the objective already exists, changes the criterion of that objective and returns `false`. If the criterion was not specified but the objective already exists, returns the current criterion. -If the objective was added, returns `true`. If nothing is affected, returns `null` +Returns `true` if the objective was created, or `null` if an objective with the specified name already exists. Throws `unknown_criterion` if criterion doesn't exist. @@ -33,6 +32,15 @@ for the objective. Sets display location for a specified `objective`. If `objective` is `null`, then display is cleared. If objective is invalid, returns `null`. +### `scoreboard_property(objective, property)` `scoreboard_property(objective, property, value)` + +Reads a property of an `objective` or sets it to a `value` if specified. Available properties are: + +* `criterion` +* `display_name` (Formatted text supported) +* `display_slot`: When reading, returns a list of slots this objective is displayed in, when modifying, displays the objective in the specified slot +* `render_type`: Either `'integer'` or `'hearts'`, defaults to `'integer'` if invalid value specified + # Team ### `team_list()`, `team_list(team)` diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index bdc17d72..eb453bcd 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -239,6 +239,11 @@ map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]- not fact rand + not fact rand reset_seed sin cos tan asin acos atan atan2 sinh cosh tanh sec csc sech csch cot acot coth asinh acosh atanh rad deg log log10 log1p @@ -46,7 +46,7 @@ var undef vars if loop map filter first all c_for for while reduce continue break - import call outer type hash_code bool number str length rand sleep time profile_expr lower upper title replace replace_first + import call outer type hash_code bool number str length sleep time profile_expr lower upper title replace replace_first block pos pos_offset solid air liquid flammable @@ -62,7 +62,7 @@ display_title - scoreboard scoreboard_add scoreboard_remove scoreboard_display + scoreboard scoreboard_add scoreboard_remove scoreboard_display scoreboard_property team_add team_list team_remove team_leave team_property diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index cb95e860..7aa7ec0f 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -313,6 +313,11 @@ public class Expression addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); } + public void addMathematicalUnaryIntFunction(String name, Function fun) + { + addUnaryFunction(name, (v) -> new NumericValue(fun.apply(NumericValue.asNumber(v).getDouble()))); + } + public void addMathematicalBinaryFunction(String name, BiFunction fun) { addBinaryFunction(name, (w, v) -> diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 1cae3c97..255f76d4 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -42,6 +42,10 @@ public abstract class ScriptHost return randomizers.computeIfAbsent(aLong, Random::new); } + public boolean resetRandom(long aLong) { + return randomizers.remove(aLong) != null; + } + public static class ModuleData { Module parent; diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 3f79856b..0537e532 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -3,6 +3,7 @@ package carpet.script.api; import carpet.CarpetServer; import carpet.CarpetSettings; import carpet.fakes.MinecraftServerInterface; +import carpet.fakes.ServerWorldInterface; import carpet.fakes.StatTypeInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; @@ -169,6 +170,11 @@ public class Auxiliary { { expression.addLazyFunction("sound", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; + if (lv.size() == 0) + { + Value ret = ListValue.wrap(Registry.SOUND_EVENT.getIds().stream().map(ValueConversions::of)); + return (_c, _t) -> ret; + } String rawString = lv.get(0).evalValue(c).getString(); Identifier soundName = new Identifier(rawString); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); @@ -206,6 +212,12 @@ public class Auxiliary { expression.addLazyFunction("particle", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; + if (lv.size() == 0) + { + Value ret = ListValue.wrap(Registry.PARTICLE_TYPE.getIds().stream().map(ValueConversions::of)); + return (_c, _t) -> ret; + } + MinecraftServer ms = cc.s.getMinecraftServer(); ServerWorld world = cc.s.getWorld(); Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1); @@ -1233,7 +1245,7 @@ public class Auxiliary { RegistryKey registryKey2 = RegistryKey.of(Registry.DIMENSION, registryKey.getValue()); DimensionType dimensionType3 = entry.getValue().getDimensionType(); ChunkGenerator chunkGenerator3 = entry.getValue().getChunkGenerator(); - UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, saveProperties.getMainWorldProperties()); + UnmodifiableLevelProperties unmodifiableLevelProperties = new UnmodifiableLevelProperties(saveProperties, ((ServerWorldInterface) server.getOverworld()).getWorldPropertiesCM()); ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false); server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder())); existing_worlds.put(registryKey2, serverWorld2); diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java index 0ec99dc0..d43dede8 100644 --- a/src/main/java/carpet/script/api/Scoreboards.java +++ b/src/main/java/carpet/script/api/Scoreboards.java @@ -35,6 +35,8 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; public class Scoreboards { @@ -79,16 +81,27 @@ public class Scoreboards { Value ret = ListValue.wrap(scoreboard.getAllPlayerScores(objective).stream().map(s -> new StringValue(s.getPlayerName())).collect(Collectors.toList())); return (_c, _t) -> ret; } + String key = getScoreboardKeyFromValue(lv.get(1).evalValue(c)); - if (!scoreboard.playerHasObjective(key, objective) && lv.size()==2) + if(lv.size()==2) { + if(scoreboard.playerHasObjective(key, objective)) { + return (_c,_t) -> NumericValue.of(scoreboard.getPlayerScore(key,objective).getScore()); + } return LazyValue.NULL; - ScoreboardPlayerScore scoreboardPlayerScore = scoreboard.getPlayerScore(key, objective); - Value retval = new NumericValue(scoreboardPlayerScore.getScore()); - if (lv.size() > 2) - { - scoreboardPlayerScore.setScore(NumericValue.asNumber(lv.get(2).evalValue(c)).getInt()); } - return (_c, _t) -> retval; + + Value value = lv.get(2).evalValue(c); + if(value.isNull()) { + ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective); + scoreboard.resetPlayerScore(key,objective); + return (_c,_t) -> NumericValue.of(score.getScore()); + } + if (value instanceof NumericValue) { + ScoreboardPlayerScore score = scoreboard.getPlayerScore(key, objective); + score.setScore(NumericValue.asNumber(value).getInt()); + return (_c,_t) -> NumericValue.of(score.getScore()); + } + throw new InternalExpressionException("'scoreboard' requires a number or null as the third parameter"); }); expression.addLazyFunction("scoreboard_remove", -1, (c, t, lv)-> @@ -139,6 +152,7 @@ public class Scoreboards { ScoreboardObjective objective = scoreboard.getObjective(objectiveName); if (objective != null) { + c.host.issueDeprecation("reading or modifying an objective's criterion with scoreboard_add"); if(lv.size() == 1) return (_c, _t) -> StringValue.of(objective.getCriterion().getName()); if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.NULL; ((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective); @@ -152,6 +166,72 @@ public class Scoreboards { return LazyValue.TRUE; }); + expression.addLazyFunction("scoreboard_property", -1, (c, t, lv) -> + { + if(lv.size() < 2) throw new InternalExpressionException("'scoreboard_property' requires at least two parameters"); + CarpetContext cc = (CarpetContext)c; + Scoreboard scoreboard = cc.s.getMinecraftServer().getScoreboard(); + ScoreboardObjective objective = scoreboard.getObjective(lv.get(0).evalValue(c).getString()); + if(objective == null) return LazyValue.NULL; + + boolean modify = lv.size() > 2; + Value setValue = null; + if(modify) { + setValue = lv.get(2).evalValue(c); + } + String property = lv.get(1).evalValue(c).getString(); + switch (property) { + case "criterion": + if(modify) { + ScoreboardCriterion criterion = ScoreboardCriterion.createStatCriterion(setValue.getString()).orElse(null); + if (criterion==null) throw new InternalExpressionException("Unknown scoreboard criterion: "+ setValue.getString()); + if(objective.getCriterion().equals(criterion) || lv.size() == 1) return LazyValue.FALSE; + ((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().get(objective.getCriterion()).remove(objective); + ((ScoreboardObjective_scarpetMixin) objective).setCriterion(criterion); + (((Scoreboard_scarpetMixin)scoreboard).getObjectivesByCriterion().computeIfAbsent(criterion, (criterion1) -> Lists.newArrayList())).add(objective); + scoreboard.updateObjective(objective); + return LazyValue.TRUE; + } + return (_c, _t) -> StringValue.of(objective.getCriterion().getName()); + case "display_name": + if(modify) { + Text text = (setValue instanceof FormattedTextValue)?((FormattedTextValue) setValue).getText():new LiteralText(setValue.getString()); + objective.setDisplayName(text); + return LazyValue.TRUE; + } + return (_c, _t) -> new FormattedTextValue(objective.getDisplayName()); + case "display_slot": + if(modify) { + int slotId = Scoreboard.getDisplaySlotId(setValue.getString()); + if(slotId == -1) throw new InternalExpressionException("Unknown scoreboard display slot: " + setValue.getString()); + if(objective.equals(scoreboard.getObjectiveForSlot(slotId))) { + return LazyValue.FALSE; + } + scoreboard.setObjectiveSlot(slotId,objective); + return LazyValue.TRUE; + } + + List slots = new ArrayList<>(); + for(int i = 0; i < 19; i++) { + if (scoreboard.getObjectiveForSlot(i) == objective) { + String slotName = Scoreboard.getDisplaySlotName(i); + slots.add(StringValue.of(slotName)); + } + } + return (_c, _t) -> ListValue.wrap(slots); + case "render_type": + if(modify) { + ScoreboardCriterion.RenderType renderType = ScoreboardCriterion.RenderType.getType(setValue.getString().toLowerCase()); + if(objective.getRenderType().equals(renderType)) return LazyValue.FALSE; + objective.setRenderType(renderType); + return LazyValue.TRUE; + } + return (_c, _t) -> StringValue.of(objective.getRenderType().getName()); + default: + throw new InternalExpressionException("scoreboard property '" + property + "' is not a valid property"); + } + }); + expression.addLazyFunction("scoreboard_display", 2, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; @@ -410,7 +490,7 @@ public class Scoreboards { team.setSuffix(suffix); break; default: - throw new InternalExpressionException("team property " + propertyVal.getString() + " is not a valid property"); + throw new InternalExpressionException("team property '" + propertyVal.getString() + "' is not a valid property"); } return LazyValue.TRUE; }); diff --git a/src/main/java/carpet/script/language/Arithmetic.java b/src/main/java/carpet/script/language/Arithmetic.java index cb8d5281..c5e64034 100644 --- a/src/main/java/carpet/script/language/Arithmetic.java +++ b/src/main/java/carpet/script/language/Arithmetic.java @@ -60,9 +60,9 @@ public class Arithmetic { expression.addMathematicalUnaryFunction("log1p", x -> Math.log1p(x)/Math.log(2)); expression.addMathematicalUnaryFunction("sqrt", Math::sqrt); expression.addMathematicalUnaryFunction("abs", Math::abs); - expression.addMathematicalUnaryFunction("round", (d) -> (double)Math.round(d)); - expression.addMathematicalUnaryFunction("floor", Math::floor); - expression.addMathematicalUnaryFunction("ceil", Math::ceil); + expression.addMathematicalUnaryIntFunction("round", Math::round); + expression.addMathematicalUnaryIntFunction("floor", n -> (long)Math.floor(n)); + expression.addMathematicalUnaryIntFunction("ceil", n -> (long)Math.ceil(n)); expression.addLazyFunction("mandelbrot", 3, (c, t, lv) -> { double a0 = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java index 4655f9d6..dc2a75df 100644 --- a/src/main/java/carpet/script/language/Sys.java +++ b/src/main/java/carpet/script/language/Sys.java @@ -210,6 +210,10 @@ public class Sys { Value retval = new NumericValue(NumericValue.asNumber(argument).getDouble()*randomizer.nextDouble()); return (cc, tt) -> retval; }); + expression.addLazyFunction("reset_seed", 1, (c, t, lv) -> { + boolean gotIt = c.host.resetRandom(NumericValue.asNumber(lv.get(0).evalValue(c)).getLong()); + return gotIt?LazyValue.TRUE:LazyValue.FALSE; + }); expression.addLazyFunction("perlin", -1, (c, t, lv) -> { diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 573ed02c..389ede83 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -141,12 +141,12 @@ public abstract class Value implements Comparable , Cloneable public void assertAssignable() { - if (boundVariable == null || boundVariable.startsWith("_")) + if (boundVariable == null)// || boundVariable.startsWith("_")) { - if (boundVariable != null) + /*if (boundVariable != null) { throw new InternalExpressionException(boundVariable+ " cannot be assigned a new value"); - } + }*/ throw new InternalExpressionException(getString()+ " is not a variable"); }