diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 489017bd..d9209f5a 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -398,6 +398,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: +
+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. @@ -517,6 +594,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/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index b3ab8745..fdc3e82c 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -792,7 +792,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). diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index bbd2872e..5e013034 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -177,7 +177,7 @@ Returns `true` if en entity is standing on firm ground and falling down due to t ### `query(e, 'name'), query(e, 'display_name'), query(e, 'custom_name'), query(e, 'type')` -String of entity name +String of entity name or formatted text in the case of `display_name`
 query(e,'name')  => Leatherworker
diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md
index 07d6993a..66c8ff17 100644
--- a/docs/scarpet/language/Containers.md
+++ b/docs/scarpet/language/Containers.md
@@ -23,12 +23,12 @@ In case to simplify the access with nested objects, you can add chain of address
 than calling it multiple times. `get(get(foo,a),b)` is equivalent to `get(foo, a, b)`, or `foo:a:b`.
 
 
-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)` @@ -100,13 +100,13 @@ of the existing elements. Use `replace` to remove and replace existing element. and merges keys from `value` with the compound tag under the path
-a = l(1, 2, 3); put(a, 1, 4); a  => [1, 4, 3]
-a = l(1, 2, 3); put(a, null, 4); a  => [1, 2, 3, 4]
-a = l(1, 2, 3); put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
-a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a  => [1, 2, 3, 4, 5, 6]
-a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a  => [1, 4, 5, 6, 2, 3]
-a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
-a = m(1,2,3,4); put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
+a = [1, 2, 3]; put(a, 1, 4); a  => [1, 4, 3]
+a = [1, 2, 3]; put(a, null, 4); a  => [1, 2, 3, 4]
+a = [1, 2, 3]; put(a, 1, 4, 'insert'); a  => [1, 4, 2, 3]
+a = [1, 2, 3]; put(a, null, [4, 5, 6], 'extend'); a  => [1, 2, 3, 4, 5, 6]
+a = [1, 2, 3]; put(a, 1, [4, 5, 6], 'extend'); a  => [1, 4, 5, 6, 2, 3]
+a = [[0,0,0],[0,0,0],[0,0,0]]; put(a:1, 1, 1); a  => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+a = {1,2,3,4}; put(a, 5, null); a  => {1: null, 2: null, 3: null, 4: null, 5: null}
 tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag  => {BlockData:{Properties:[1,2,3,4]}}
 tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag  => {a:[{lvl:1},{lvl:1},{lvl:1}]}
 tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
@@ -187,10 +187,10 @@ other parts. In that case consecutive calls to `slice` will refer to index `0` t
 cannot go back nor track where they are in the sequence (see examples).
 
 
-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]]
 
@@ -225,7 +225,7 @@ Primarily to be used in higher order functions
 range(10)  => [...]
-l(range(10))  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+[range(10)]  => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 map(range(10),_*_)  => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 reduce(range(10),_a+_, 0)  => 45
 range(5,10)  => [5, 6, 7, 8, 9]
diff --git a/docs/scarpet/language/LoopsAndHigherOrderFunctions.md b/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
index fa3ab931..95d018ed 100644
--- a/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
+++ b/docs/scarpet/language/LoopsAndHigherOrderFunctions.md
@@ -124,12 +124,12 @@ to `all(list,expr) <=> for(list,expr)==length(list)`. `expr` also receives bound
 and `continue` have no sense and cannot be used inside of `expr` body.
 
 
-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
diff --git a/docs/scarpet/language/Operators.md b/docs/scarpet/language/Operators.md
index 262f4cc2..4780124d 100644
--- a/docs/scarpet/language/Operators.md
+++ b/docs/scarpet/language/Operators.md
@@ -16,7 +16,7 @@ in [User Defined Functions and Program Control Flow](docs/scarpet/language/Funct
 1357-5 => 1352
 1357-'5' => 137
 3*'foo'-'o' => 'fff'
-l(1,3,5)+7 => l(8,10,12)
+[1,3,5]+7 => [8,10,12]
 
As you can see, values can behave differently when mixed with other types in the same expression. @@ -76,8 +76,8 @@ In Minecraft API portion `entity ~ feature` is a shortcode for `query(entity,fea any extra arguments.
-l(1,2,3) ~ 2  => 1
-l(1,2,3) ~ 4  => null
+[1,2,3] ~ 2  => 1
+[1,2,3] ~ 4  => null
 
 'foobar' ~ 'baz'  => null
 'foobar' ~ '.b'  => 'ob'
@@ -103,10 +103,10 @@ Or an example to find if a player has specific enchantment on a held axe (either
 
 
 global_get_enchantment(p, ench) -> (
-$   for(l('main','offhand'),
+$   for(['main','offhand'],
 $      holds = query(p, 'holds', _);
 $      if( holds,
-$         l(what, count, nbt) = holds;
+$         [what, count, nbt] = holds;
 $         if( what ~ '_axe' && nbt ~ ench,
 $            lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
 $         )
@@ -143,8 +143,8 @@ Examples:
 'foo'*3 => 'foofoofoo'
 'foofoofoo' / 3 => 'foo'
 'foofoofoo'-'o' => 'fff'
-l(1,2,3)+1  => l(2,3,4)
-b = l(100,63,100); b+l(10,0,10)  => l(110,63,110)
+[1,2,3]+1  => [2,3,4]
+b = [100,63,100]; b+[10,0,10]  => [110,63,110]
 {'a' -> 1} + {'b' -> 2} => {'a' -> 1, 'b' -> 2}
 
@@ -186,11 +186,12 @@ second operand is not necessary, it won't be evaluated, which means one can use case of success returns first positive operand (`||`) or last one (`&&`).
-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 = <> +=` @@ -204,11 +205,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 - +` @@ -226,10 +227,10 @@ Require a number, flips the sign. One way to assert it's a number is by crashing flips boolean condition of the expression. Equivalent of `bool(expr)==false`
-!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
 
diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 1f3768e6..bdc17d72 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -14,30 +14,31 @@ as well as minecraft related concepts like `block`, `entity`, `nbt`, `text`. ### `bool(expr)` -Returns a boolean context of the expression. Note that there are no true/false values in scarpet. `true` is -alias of 1, and `false` is 0\. Bool is also interpreting string values as boolean, which is different from other +Returns a boolean context of the expression. +Bool is also interpreting string values as boolean, which is different from other places where boolean context can be used. This can be used in places where API functions return string values to -represent binary values +represent binary values.
-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
@@ -65,15 +66,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'
 
@@ -234,7 +235,7 @@ same random object. Scarpet keeps track of up to 1024 custom random number gener
 map(range(10), floor(rand(10))) => [5, 8, 0, 6, 9, 3, 9, 9, 1, 8]
-map(range(10), bool(rand(2))) => [1, 1, 1, 0, 0, 1, 1, 0, 0, 0]
+map(range(10), bool(rand(2))) => [false, false, true, false, false, false, true, false, true, false]
 map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6]
 
diff --git a/docs/scarpet/language/VariablesAndConstants.md b/docs/scarpet/language/VariablesAndConstants.md index 2afb874d..a7657f4d 100644 --- a/docs/scarpet/language/VariablesAndConstants.md +++ b/docs/scarpet/language/VariablesAndConstants.md @@ -3,8 +3,8 @@ `scarpet` provides a number of constants that can be used literally in scripts * `null`: nothing, zilch, not even false -* `true`: pure true, or just 1 (one) -* `false`: false truth, or true falsth, 0 (zero) actually +* `true`: pure true, can act as `1` +* `false`: false truth, or true falsth, equals to `0` * `pi`: for the fans of perimeters, its a perimeter of an apple pi of diameter 1\. About 3.14 * `euler`: clever guy. Derivative of its exponent is goto 1\. About 2.72 diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 4186fa25..1c3251b3 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -17,9 +17,9 @@ brightness chunk_tickets convert_date crafting_remaining_item +create_datapack create_marker current_dimension -custom_dimension day_time delete_file destroy @@ -28,6 +28,7 @@ display_title draw_shape drop_item emitted_light +enable_hidden_dimensions encode_nbt entity_area entity_event diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index b7735dc6..5ae29a0f 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -55,7 +55,7 @@ set without_updates blocks_movement block_sound material map_colour block_state block_list block_tags block_data poi set_poi place_item set_biome biome loaded_status generation_status inhabited_time spawn_potential chunk_tickets - structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension + structure_eligibility structures structure_references set_structure reset_chunk reload_chunk create_datapack enable_hidden_dimensions add_chunk_ticket convert_date unix_time diff --git a/gradle.properties b/gradle.properties index a9261de6..d74a5b2d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx1G fabric_version=0.29.4+1.16 # Mod Properties - mod_version = 1.4.27 + mod_version = 1.4.28 maven_group = carpet archives_base_name = fabric-carpet diff --git a/settings.gradle b/settings.gradle index 5b60df3d..f91a4fe7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,5 @@ pluginManagement { repositories { - jcenter() maven { name = 'Fabric' url = 'https://maven.fabricmc.net/' diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 83c4c3ec..1435dc56 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -42,7 +42,7 @@ import static carpet.settings.RuleCategory.CLIENT; @SuppressWarnings("CanBeFinal") public class CarpetSettings { - public static final String carpetVersion = "1.4.27+v210217"; + public static final String carpetVersion = "1.4.28+v210224"; public static final Logger LOG = LogManager.getLogger("carpet"); public static boolean skipGenerationChecks = false; public static boolean impendingFillSkipUpdates = false; @@ -413,20 +413,26 @@ public class CarpetSettings private static class ModulePermissionLevel extends Validator { @Override public String validate(ServerCommandSource source, ParsedRule currentRule, String newValue, String string) { - CarpetSettings.runPermissionLevel = SettingsManager.getCommandLevel(newValue); + int permissionLevel = SettingsManager.getCommandLevel(newValue); + if (source != null && !source.hasPermissionLevel(permissionLevel)) + return null; + CarpetSettings.runPermissionLevel = permissionLevel; + CarpetServer.settingsManager.notifyPlayersCommandsChanged(); return newValue; } @Override - public String description() { return "Also controls permission level of commands executed via `run()`";} + public String description() { return "When changing the rule, you must at least have the permission level you are trying to give it";} } @Rule( desc = "Enables restrictions for arbitrary code execution with scarpet", extra = { "Users that don't have this permission level", - "won't be able to load apps or /script run" + "won't be able to load apps or /script run.", + "It is also the permission level apps will", + "have when running commands with run()" }, - category = {COMMAND, SCARPET}, - validate = ModulePermissionLevel.class + category = {SCARPET}, + validate = {Validator._COMMAND_LEVEL_VALIDATOR.class, ModulePermissionLevel.class} ) public static String commandScriptACE = "ops"; @@ -532,6 +538,14 @@ public class CarpetSettings ) public static int maxEntityCollisions = 0; + @Rule( + desc = "Customizable server list ping (Multiplayer menu) playerlist sample limit", + options = {"0", "12", "20", "40"}, + category = CREATIVE, + strict = false, + validate = Validator.NONNEGATIVE_NUMBER.class + ) + public static int pingPlayerListLimit = 12; /* @Rule( @@ -860,7 +874,7 @@ public class CarpetSettings @Rule( desc = "Lightning kills the items that drop when lightning kills an entity", - extra = {"Setting to true will prevent lightning from killing drops", "Fixes (https://bugs.mojang.com/browse/MC-195640)."}, + extra = {"Setting to true will prevent lightning from killing drops", "Fixes [MC-206922](https://bugs.mojang.com/browse/MC-206922)."}, category = {BUGFIX} ) public static boolean lightningKillsDropsFix = false; diff --git a/src/main/java/carpet/commands/PlayerCommand.java b/src/main/java/carpet/commands/PlayerCommand.java index 9aa9297b..ee52afe4 100644 --- a/src/main/java/carpet/commands/PlayerCommand.java +++ b/src/main/java/carpet/commands/PlayerCommand.java @@ -23,6 +23,7 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec2f; import net.minecraft.util.math.Vec3d; @@ -271,7 +272,13 @@ public class PlayerCommand Messenger.m(context.getSource(), "rb Player name: "+playerName+" is too long"); return 0; } + MinecraftServer server = source.getMinecraftServer(); + if (!World.isInBuildLimit(new BlockPos(pos.x, pos.y, pos.z))) + { + Messenger.m(context.getSource(), "rb Player "+playerName+" cannot be placed outside of the world"); + return 0; + } PlayerEntity player = EntityPlayerMPFake.createFake(playerName, server, pos.x, pos.y, pos.z, facing.y, facing.x, dimType, mode); if (player == null) { diff --git a/src/main/java/carpet/fakes/MinecraftServerInterface.java b/src/main/java/carpet/fakes/MinecraftServerInterface.java index 08fa24ae..a4422592 100644 --- a/src/main/java/carpet/fakes/MinecraftServerInterface.java +++ b/src/main/java/carpet/fakes/MinecraftServerInterface.java @@ -1,5 +1,6 @@ package carpet.fakes; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; @@ -13,4 +14,5 @@ public interface MinecraftServerInterface void forceTick(BooleanSupplier sup); LevelStorage.Session getCMSession(); Map, ServerWorld> getCMWorlds(); + ServerResourceManager getResourceManager(); } diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java index 3130f30a..1c124ce5 100644 --- a/src/main/java/carpet/helpers/OptimizedExplosion.java +++ b/src/main/java/carpet/helpers/OptimizedExplosion.java @@ -107,20 +107,22 @@ public class OptimizedExplosion explosionSound++; + Entity explodingEntity = eAccess.getEntity(); for (int k2 = 0; k2 < entitylist.size(); ++k2) { Entity entity = entitylist.get(k2); - if (entity == eAccess.getEntity()) { + + if (entity == explodingEntity) { // entitylist.remove(k2); removeFast(entitylist, k2); k2--; continue; } - if (entity instanceof TntEntity && - entity.getX() == eAccess.getEntity().getX() && - entity.getY() == eAccess.getEntity().getY() && - entity.getZ() == eAccess.getEntity().getZ()) { + if (entity instanceof TntEntity && explodingEntity != null && + entity.getX() == explodingEntity.getX() && + entity.getY() == explodingEntity.getY() && + entity.getZ() == explodingEntity.getZ()) { if (eLogger != null) { eLogger.onEntityImpacted(entity, new Vec3d(0,-0.9923437498509884d, 0)); } diff --git a/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java b/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java new file mode 100644 index 00000000..79e2e27d --- /dev/null +++ b/src/main/java/carpet/mixins/MinecraftServer_pingPlayerSampleLimit.java @@ -0,0 +1,19 @@ +package carpet.mixins; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import carpet.CarpetSettings; +import net.minecraft.server.MinecraftServer; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServer_pingPlayerSampleLimit +{ + + @ModifyConstant(method = "tick", constant = @Constant(intValue = 12), require = 0, allow = 1) + private int modifyPlayerSampleLimit(int value) + { + return CarpetSettings.pingPlayerListLimit; + } +} diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java index a4934a7f..1b71dfa9 100644 --- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java +++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java @@ -2,6 +2,7 @@ package carpet.mixins; import carpet.fakes.MinecraftServerInterface; import carpet.helpers.TickSpeed; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; import net.minecraft.server.world.ServerWorld; @@ -44,6 +45,8 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut @Shadow @Final private Map, ServerWorld> worlds; + @Shadow private ServerResourceManager serverResourceManager; + @Override public void forceTick(BooleanSupplier isAhead) { @@ -58,6 +61,11 @@ public abstract class MinecraftServer_scarpetMixin extends ReentrantThreadExecut return session; } + @Override + public ServerResourceManager getResourceManager() { + return serverResourceManager; + } + @Override public Map, ServerWorld> getCMWorlds() { diff --git a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java index a272f7bd..de999990 100644 --- a/src/main/java/carpet/mixins/PlayerManager_coreMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_coreMixin.java @@ -25,10 +25,4 @@ public class PlayerManager_coreMixin CarpetServer.onPlayerLoggedIn(player); PLAYER_CONNECTS.onPlayerEvent(player); } - - @Inject(method = "onDataPacksReloaded", at = @At("HEAD")) - private void onResourcesRefreshedReloadCommand(CallbackInfo ci) - { - CarpetServer.onReload(server); - } } diff --git a/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java new file mode 100644 index 00000000..bf460603 --- /dev/null +++ b/src/main/java/carpet/mixins/ReloadCommand_reloadAppsMixin.java @@ -0,0 +1,23 @@ +package carpet.mixins; + +import carpet.CarpetServer; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.command.ReloadCommand; +import net.minecraft.server.command.ServerCommandSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ReloadCommand.class) +public class ReloadCommand_reloadAppsMixin { + //method_13530(Lcom/mojang/brigadier/context/CommandContext;)I + // internal of register. + @SuppressWarnings("UnresolvedMixinReference") + @Inject(method = "method_13530", at = @At("TAIL")) + private static void onReload(CommandContext context, CallbackInfoReturnable cir) + { + // can't fetch here the reference to the server + CarpetServer.onReload(context.getSource().getMinecraftServer()); + } +} diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 8634ca00..fc9eb5e2 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -26,6 +26,7 @@ import net.minecraft.util.WorldSavePath; import net.minecraft.util.math.BlockPos; import java.io.IOException; +import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -136,7 +137,8 @@ public class CarpetScriptServer { try { Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); - Files.createDirectories(folder); + if (!Files.exists(folder)) + Files.createDirectories(folder); Optional scriptPath = Files.list(folder) .filter(script -> @@ -149,7 +151,8 @@ public class CarpetScriptServer if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) { Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); - Files.createDirectories(globalFolder); + if (!Files.exists(globalFolder)) + Files.createDirectories(globalFolder); scriptPath = Files.walk(globalFolder) .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") || (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl"))) @@ -194,7 +197,8 @@ public class CarpetScriptServer } try { Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); - Files.createDirectories(worldScripts); + if (!Files.exists(worldScripts)) + Files.createDirectories(worldScripts); Files.list(worldScripts) .filter(f -> f.toString().endsWith(".sc")) .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT))); @@ -202,8 +206,9 @@ public class CarpetScriptServer if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)) { Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); - Files.createDirectories(globalScripts); - Files.walk(globalScripts) + if (!Files.exists(globalScripts)) + Files.createDirectories(globalScripts); + Files.walk(globalScripts, FileVisitOption.FOLLOW_LINKS) .filter(f -> f.toString().endsWith(".sc")) .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT))); } diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index df5bb8df..63ebc142 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -22,6 +22,7 @@ import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; import carpet.script.utils.FixedCommandSource; +import carpet.script.utils.InputValidator; import carpet.script.utils.ScarpetJsonDeserializer; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; @@ -37,6 +38,10 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.utils.Messenger; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import net.minecraft.SharedConstants; import net.minecraft.block.BlockState; import net.minecraft.command.DataCommandStorage; import net.minecraft.entity.Entity; @@ -47,12 +52,18 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action; import net.minecraft.particle.ParticleEffect; +import net.minecraft.resource.DataPackSettings; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; +import net.minecraft.resource.ServerResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; @@ -65,13 +76,27 @@ import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.crash.CrashException; +import net.minecraft.util.Util; +import net.minecraft.util.WorldSavePath; +import net.minecraft.util.dynamic.RegistryOps; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.DynamicRegistryManager; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; +import net.minecraft.world.SaveProperties; import net.minecraft.world.World; +import net.minecraft.world.biome.source.BiomeAccess; +import net.minecraft.world.border.WorldBorderListener; +import net.minecraft.world.dimension.DimensionOptions; +import net.minecraft.world.dimension.DimensionType; +import net.minecraft.world.gen.GeneratorOptions; +import net.minecraft.world.gen.chunk.ChunkGenerator; +import net.minecraft.world.level.UnmodifiableLevelProperties; +import net.minecraft.world.level.storage.LevelStorage; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Triple; @@ -80,6 +105,13 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -98,7 +130,7 @@ import static java.lang.Math.min; public class Auxiliary { public static final String MARKER_STRING = "__scarpet_marker"; private static final Map mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); - public static final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create(); public static String recognizeResource(Value value, boolean isFloder) { @@ -849,7 +881,7 @@ public class Auxiliary { exc = e.getCause(); throw new ThrowStatement("Failed to read JSON file: "+exc.getMessage(), Throwables.JSON_READ); } - Value parsedJson = gson.fromJson(json, Value.class); + Value parsedJson = GSON.fromJson(json, Value.class); if (parsedJson == null) retVal = Value.NULL; else @@ -888,7 +920,7 @@ public class Auxiliary { } else if (fdesc.getMiddle().equals("json")) { - List data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson())); + List data = Collections.singletonList(GSON.toJson(lv.get(2).evalValue(c).toJson())); ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle()); success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data); } @@ -1066,8 +1098,179 @@ public class Auxiliary { if (old_nbt == null) return LazyValue.NULL; return (_c, _t) -> new NBTSerializableValue(old_nbt); }); + + // script run create_datapack('foo', {'foo' -> {'bar.json' -> {'c' -> true,'d' -> false,'e' -> {'foo' -> [1,2,3]},'a' -> 'foobar','b' -> 5}}}) + expression.addLazyFunction("create_datapack", 2, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + String origName = lv.get(0).evalValue(c).getString(); + String name = InputValidator.validateSimpleString(origName, true); + MinecraftServer server = cc.s.getMinecraftServer(); + for (String dpName : server.getDataPackManager().getNames()) + { + if (dpName.equalsIgnoreCase("file/"+name+".zip") || + dpName.equalsIgnoreCase("file/"+name)) + return LazyValue.NULL; + + } + Value dpdata = lv.get(1).evalValue(c); + if (!(dpdata instanceof MapValue)) + throw new InternalExpressionException("datapack data needs to be a valid map type"); + ResourcePackManager packManager = server.getDataPackManager(); + Path dbFloder = server.getSavePath(WorldSavePath.DATAPACKS); + Path packFloder = dbFloder.resolve(name+".zip"); + if (Files.exists(packFloder) || Files.exists(dbFloder.resolve(name))) return LazyValue.NULL; + Boolean [] successful = new Boolean[]{true}; + server.submitAndJoin( () -> + { + try { + //Files.createDirectory(packFloder); + try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + packFloder.toUri().toString()), ImmutableMap.of("create", "true"))) { + Path zipRoot = zipfs.getPath("/"); + zipValueToJson(zipRoot.resolve("pack.mcmeta"), MapValue.wrap( + ImmutableMap.of(StringValue.of("pack"), MapValue.wrap(ImmutableMap.of( + StringValue.of("pack_format"), new NumericValue(SharedConstants.getGameVersion().getPackVersion()), + StringValue.of("description"), StringValue.of(name), + StringValue.of("source"), StringValue.of("scarpet") + ))) + )); + walkTheDPMap((MapValue) dpdata, zipRoot); + } + packManager.scanPacks(); + ResourcePackProfile resourcePackProfile = packManager.getProfile("file/" + name + ".zip"); + if (resourcePackProfile == null || packManager.getEnabledProfiles().contains(resourcePackProfile)) { + throw new IOException(); + } + List list = Lists.newArrayList(packManager.getEnabledProfiles()); + resourcePackProfile.getInitialPosition().insert(list, resourcePackProfile, p -> p, false); + + + server.reloadResources(list.stream().map(ResourcePackProfile::getName).collect(Collectors.toList())). + exceptionally(exc -> { + successful[0] = false; + return null; + }).join(); + if (!successful[0]) { + throw new IOException(); + } + } catch (IOException e) + { + successful[0] = false; + try { + FileUtils.forceDelete(packFloder.toFile()); + } catch (IOException ignored) { + throw new InternalExpressionException("Failed to install a datapack and failed to clean up after it"); + } + + } + }); + return successful[0]?LazyValue.TRUE:LazyValue.FALSE; + }); + + expression.addLazyFunction("enable_hidden_dimensions", 0, (c, t, lv) -> { + CarpetContext cc = (CarpetContext)c; + // from minecraft.server.Main.main + MinecraftServer server = cc.s.getMinecraftServer(); + LevelStorage.Session session = ((MinecraftServerInterface)server).getCMSession(); + DataPackSettings dataPackSettings = session.getDataPackSettings(); + ResourcePackManager resourcePackManager = server.getDataPackManager(); + DataPackSettings dataPackSettings2 = MinecraftServer.loadDataPacks(resourcePackManager, dataPackSettings == null ? DataPackSettings.SAFE_MODE : dataPackSettings, false); + ServerResourceManager serverRM = ((MinecraftServerInterface)server).getResourceManager(); + ReloadableResourceManagerImpl resourceManager = (ReloadableResourceManagerImpl) serverRM.getResourceManager(); + + //believe the other one will fillup based on the datapacks only. + resourceManager.close(); + resourcePackManager.createResourcePacks().forEach(resourceManager::addPack); + + //not sure its needed, but doesn't seem to have a negative effect and might be used in some custom shtuff + serverRM.loadRegistryTags(); + + RegistryOps registryOps = RegistryOps.of(NbtOps.INSTANCE, serverRM.getResourceManager(), (DynamicRegistryManager.Impl) server.getRegistryManager()); + SaveProperties saveProperties = session.readLevelProperties(registryOps, dataPackSettings2); + if (saveProperties == null) return LazyValue.NULL; + //session.backupLevelDataFile(server.getRegistryManager(), saveProperties); // no need + + // MinecraftServer.createWorlds + // save properties should now contain dimension settings + GeneratorOptions generatorOptions = saveProperties.getGeneratorOptions(); + boolean bl = generatorOptions.isDebugWorld(); + long l = generatorOptions.getSeed(); + long m = BiomeAccess.hashSeed(l); + Map, ServerWorld> existing_worlds = ((MinecraftServerInterface)server).getCMWorlds(); + List addeds = new ArrayList<>(); + for (Map.Entry, DimensionOptions> entry : generatorOptions.getDimensions().getEntries()) { + RegistryKey registryKey = entry.getKey(); + if (!existing_worlds.containsKey(registryKey)) + { + addeds.add(ValueConversions.of(registryKey.getValue())); + 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()); + ServerWorld serverWorld2 = new ServerWorld(server, Util.getMainWorkerExecutor(), session, unmodifiableLevelProperties, registryKey2, dimensionType3, WorldTools.NOOP_LISTENER, chunkGenerator3, bl, m, ImmutableList.of(), false); + server.getOverworld().getWorldBorder().addListener(new WorldBorderListener.WorldBorderSyncer(serverWorld2.getWorldBorder())); + existing_worlds.put(registryKey2, serverWorld2); + } + } + Value ret = ListValue.wrap(addeds); + return (_c, _t) -> ret; + }); } + private static void zipValueToJson(Path path, Value output) throws IOException + { + JsonElement element = output.toJson(); + if (element == null) + throw new InternalExpressionException("Cannot interpret "+output.getPrettyString()+" as a json object"); + String string = GSON.toJson(element); + Files.createDirectories(path.getParent()); + BufferedWriter bufferedWriter = Files.newBufferedWriter(path); + Throwable incident = null; + try + { + bufferedWriter.write(string); + } + catch (Throwable shitHappened) + { + incident = shitHappened; + throw shitHappened; + } + finally + { + if (incident != null) { + try { + bufferedWriter.close(); + } catch (Throwable otherShitHappened) { + incident.addSuppressed(otherShitHappened); + } + } else { + bufferedWriter.close(); + } + } + } + + private static void walkTheDPMap(MapValue node, Path path) throws IOException + { + Map items = node.getMap(); + for (Map.Entry entry : items.entrySet()) + { + Value val = entry.getValue(); + String strkey = entry.getKey().getString(); + Path child = path.resolve(strkey); + if (strkey.endsWith(".json")) + { + zipValueToJson(child, val); + } + else + { + if (!(val instanceof MapValue)) throw new InternalExpressionException("Value of "+strkey+" should be a map"); + Files.createDirectory(child); + walkTheDPMap((MapValue) val, child); + } + } + } + + + private static Stat getStat(StatType type, Identifier id) { T key = type.getRegistry().get(id); diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index 01fa4ea8..e3ad4353 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -93,7 +93,7 @@ public class Inventories { Item item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()).getItem(); if (lv.size() == 1) { - Value ret = ListValue.wrap(tagManager.getItems().getTagsFor(item).stream().map(ValueConversions::of).collect(Collectors.toList())); + Value ret = ListValue.wrap(tagManager.getItems().getTags().entrySet().stream().filter(e -> e.getValue().contains(item)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(1).evalValue(c).getString(); diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index 85367839..36652027 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -1127,7 +1127,8 @@ public class WorldAccess { BlockArgument blockLocator = BlockArgument.findIn(cc, lv, 0, true); if (blockLocator.offset == lv.size()) { - Value ret = ListValue.wrap(tagManager.getBlocks().getTagsFor(blockLocator.block.getBlockState().getBlock()).stream().map(ValueConversions::of).collect(Collectors.toList())); + Block target = blockLocator.block.getBlockState().getBlock(); + Value ret = ListValue.wrap(tagManager.getBlocks().getTags().entrySet().stream().filter(e -> e.getValue().contains(target)).map(e -> ValueConversions.of(e.getKey())).collect(Collectors.toList())); return (_c, _t) -> ret; } String tag = lv.get(blockLocator.offset).evalValue(c).getString(); @@ -1381,6 +1382,7 @@ public class WorldAccess { { if (lv.size() == 0) throw new InternalExpressionException("'custom_dimension' requires at least one argument"); CarpetContext cc = (CarpetContext)c; + cc.host.issueDeprecation("custom_dimension()"); String worldKey = lv.get(0).evalValue(c).getString(); Long seed = null; diff --git a/src/main/java/carpet/script/language/Sys.java b/src/main/java/carpet/script/language/Sys.java index 2f21cb4e..4655f9d6 100644 --- a/src/main/java/carpet/script/language/Sys.java +++ b/src/main/java/carpet/script/language/Sys.java @@ -48,7 +48,7 @@ public class Sys { return (cc, tt) -> Value.FALSE; } } - Value retval = new NumericValue(v.getBoolean()); + Value retval = v.getBoolean()?Value.TRUE:Value.FALSE; return (cc, tt) -> retval; }); @@ -56,7 +56,9 @@ public class Sys { { if (v instanceof NumericValue) { - return v; + NumericValue num = (NumericValue)v; + if (num.isInteger()) return new NumericValue(num.getLong()); + return new NumericValue(num.getDouble()); } try { diff --git a/src/main/java/carpet/script/utils/InputValidator.java b/src/main/java/carpet/script/utils/InputValidator.java new file mode 100644 index 00000000..67405ab3 --- /dev/null +++ b/src/main/java/carpet/script/utils/InputValidator.java @@ -0,0 +1,16 @@ +package carpet.script.utils; + +import carpet.script.exception.InternalExpressionException; + +import java.util.Locale; + +public class InputValidator { + public static String validateSimpleString(String input, boolean strict) + { + String simplified = input.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9+_]", ""); + if (simplified.isEmpty() || (strict && !simplified.equals(input))) + throw new InternalExpressionException("simple name can only contain numbers, letter and _"); + return simplified; + } + +} diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 230fbac5..79dd6b1a 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -9,6 +9,7 @@ import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; +import carpet.script.value.ValueConversions; import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; @@ -49,6 +50,7 @@ public class SystemInfo { String tlf = serverPath.getName(nodeCount-2).toString(); return StringValue.of(tlf); }); + put("world_dimensions", c -> ListValue.wrap(c.s.getMinecraftServer().getWorldRegistryKeys().stream().map(k -> ValueConversions.of(k.getValue())).collect(Collectors.toList()))); put("game_difficulty", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getDifficulty().getName())); put("game_hardcore", c -> new NumericValue(c.s.getMinecraftServer().getSaveProperties().isHardcore())); put("game_storage_format", c -> StringValue.of(c.s.getMinecraftServer().getSaveProperties().getFormatName(c.s.getMinecraftServer().getSaveProperties().getVersion()))); diff --git a/src/main/java/carpet/script/utils/WorldTools.java b/src/main/java/carpet/script/utils/WorldTools.java index c32d94fc..8d6c4b27 100644 --- a/src/main/java/carpet/script/utils/WorldTools.java +++ b/src/main/java/carpet/script/utils/WorldTools.java @@ -157,5 +157,5 @@ public class WorldTools @Override public void stop() { } } - private static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); + public static final WorldGenerationProgressListener NOOP_LISTENER = new NoopWorldGenerationProgressListener(); } diff --git a/src/main/java/carpet/script/value/BooleanValue.java b/src/main/java/carpet/script/value/BooleanValue.java new file mode 100644 index 00000000..ea67f11b --- /dev/null +++ b/src/main/java/carpet/script/value/BooleanValue.java @@ -0,0 +1,58 @@ +package carpet.script.value; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.Tag; + +public class BooleanValue extends NumericValue +{ + public static final BooleanValue FALSE = new BooleanValue(false); + public static final BooleanValue TRUE = new BooleanValue(true); + + boolean boolValue; + private BooleanValue(boolean boolval) { + super(boolval); + boolValue = boolval; + } + + @Override + public String getString() { + return boolValue?"true":"false"; + } + + @Override + public String getPrettyString() { + return getString(); + } + + @Override + public String getTypeString() { + return "bool"; + } + + @Override + public Value clone() { + return new BooleanValue(boolValue); + } + + @Override + public int hashCode() { + return Boolean.hashCode(boolValue); + } + + @Override + public Tag toTag(boolean force) { + return ByteTag.of(boolValue); + } + + @Override + public JsonElement toJson() { + return new JsonPrimitive(boolValue); + } + + @Override + public boolean isInteger() { + return true; + } +} diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 9504166a..5172c4d9 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -416,7 +416,7 @@ public class EntityValue extends Value put("motion_z", (e, a) -> new NumericValue(e.getVelocity().z)); put("on_ground", (e, a) -> new NumericValue(e.isOnGround())); put("name", (e, a) -> new StringValue(e.getName().getString())); - put("display_name", (e, a) -> new StringValue(e.getDisplayName().getString())); + put("display_name", (e, a) -> new FormattedTextValue(e.getDisplayName())); put("command_name", (e, a) -> new StringValue(e.getEntityName())); put("custom_name", (e, a) -> e.hasCustomName()?new StringValue(e.getCustomName().getString()):Value.NULL); put("type", (e, a) -> new StringValue(nameFromRegistryId(Registry.ENTITY_TYPE.getId(e.getType())))); @@ -429,7 +429,7 @@ public class EntityValue extends Value put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); put("scoreboard_tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); - put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTagsFor(e.getType()).stream().map(ValueConversions::of).collect(Collectors.toList()))); + put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTags().entrySet().stream().filter(entry -> entry.getValue().contains(e.getType())).map(entry -> ValueConversions.of(entry.getKey())).collect(Collectors.toList()))); // deprecated put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); @@ -462,7 +462,7 @@ public class EntityValue extends Value return ListValue.of(new NumericValue(look.x),new NumericValue(look.y),new NumericValue(look.z)); }); put("is_burning", (e, a) -> new NumericValue(e.isOnFire())); - //put("fire", (e, a) -> new NumericValue(e.getFire())); needs mixing + put("fire", (e, a) -> new NumericValue(e.getFireTicks())); put("silent", (e, a)-> new NumericValue(e.isSilent())); put("gravity", (e, a) -> new NumericValue(!e.hasNoGravity())); put("immune_to_fire", (e, a) -> new NumericValue(e.isFireImmune())); diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index 7fd693eb..de316668 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -341,7 +341,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf int from = normalizeIndex(fromDesc, size); if (toDesc == null) return new ListValue((Collection) getItems().subList(from, size)); - int to = normalizeIndex(toDesc, size); + int to = normalizeIndex(toDesc, size+1); if (from > to) return ListValue.of(); return new ListValue((Collection) getItems().subList(from, to)); diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java index 81375bdc..10d11f24 100644 --- a/src/main/java/carpet/script/value/MapValue.java +++ b/src/main/java/carpet/script/value/MapValue.java @@ -7,6 +7,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -16,7 +17,7 @@ import java.util.stream.Collectors; public class MapValue extends AbstractListValue implements ContainerValueInterface { - private Map map; + private final Map map; private MapValue() { @@ -268,7 +269,9 @@ public class MapValue extends AbstractListValue implements ContainerValueInterfa public JsonElement toJson() { JsonObject jsonMap = new JsonObject(); - map.forEach((k, v) -> jsonMap.add(k.getString(), v.toJson())); + List keys = new ArrayList<>(map.keySet()); + Collections.sort(keys); + keys.forEach( k -> jsonMap.add(k.getString(), map.get(k).toJson())); return jsonMap; } } diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java index 7dcec512..1e5f08a0 100644 --- a/src/main/java/carpet/script/value/NullValue.java +++ b/src/main/java/carpet/script/value/NullValue.java @@ -9,6 +9,7 @@ import net.minecraft.nbt.Tag; public class NullValue extends NumericValue // TODO check nonsingleton code { + public static final NullValue NULL = new NullValue(); @Override public String getString() { @@ -32,7 +33,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code { return new NullValue(); } - public NullValue() {super(0.0D);} + private NullValue() {super(false);} @Override public boolean equals(final Object o) diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 5f228c7d..573ed02c 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -15,12 +15,12 @@ import java.util.stream.Collectors; public abstract class Value implements Comparable, Cloneable { - public static NumericValue FALSE = new NumericValue(0); - public static NumericValue TRUE = new NumericValue(1); - public static NumericValue ZERO = FALSE; - public static NumericValue ONE = TRUE; + public static NumericValue FALSE = BooleanValue.FALSE; + public static NumericValue TRUE = BooleanValue.TRUE; + public static NumericValue ZERO = new NumericValue(0); + public static NumericValue ONE = new NumericValue(1); - public static NullValue NULL = new NullValue(); + public static NullValue NULL = NullValue.NULL; public String boundVariable; @@ -186,7 +186,7 @@ public abstract class Value implements Comparable, Cloneable int size = value.length(); int from = ListValue.normalizeIndex(fromDesc, size); if (toDesc == null) return new StringValue(value.substring(from)); - int to = ListValue.normalizeIndex(toDesc, size); + int to = ListValue.normalizeIndex(toDesc, size+1); if (from > to) return StringValue.EMPTY; return new StringValue(value.substring(from, to)); } diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 7d146cdd..4ed58ef6 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -1,4 +1,4 @@ -__command() -> +_command() -> ( print('camera: scarpet app.'); print('-------------------'); @@ -11,7 +11,7 @@ __command() -> print(' "/camera place_player - Move player to the selected point'); print(' "/camera move" - Move the selected point to players location'); print(' "/camera duration " - Set new selected path duration'); - print(' "/camera split_point>" - Split selected path in half.'); + print(' "/camera split_point" - Split selected path in half.'); print(' "/camera delete_point" - Remove current key point'); print(' "/camera trim_path" - Remove all key points from selected up'); print(''); @@ -57,6 +57,47 @@ __command() -> print(''); null ); +__config() ->{ + 'commands'->{ + ''->'_command', + ''->'_call', + 'add '->'add', + 'prepend '->'prepend', + 'duration ' -> 'duration', + 'save_as '->'save_as', + 'load '->'load', + 'interpolation '->['__interpolation',true], + 'interpolation gauss'->['__interpolation','gauss',true], + 'interpolation gauss '->_(float)->(__interpolation('gauss_'+str(float),true)), + 'repeat '->'repeat', + 'stretch '->'stretch' + }, + 'arguments'->{ + 'seconds'->{'type'->'int','suggest'->[]}, + 'last_delay'->{'type'->'int','suggest'->[]}, + 'name'->{'type'->'string','suggest'->[]}, + 'interpolation'->{'type'->'term','options'->['linear','catmull_rom']}, + 'factor'->{'type'->'int','min'->25,'max'->400}, + 'command'->{'type'->'term','options'->[ + 'start', + 'clear', + 'select', + 'place_player', + 'move', + 'split_point', + 'delete_point', + 'trim_path', + 'transpose', + 'play', + 'show', + 'hide', + 'prefer_smooth_play', + 'prefer_synced_play' + ]} + } +}; + +_call(command)->call(command); global_points = null; global_dimension = null; @@ -150,7 +191,6 @@ __assert_point_selected(validator) -> ); //select a custom interpolation method -interpolation(method) -> __interpolation(method, true); __interpolation(method, verbose) -> ( __prepare_path_if_needed() -> __prepare_path_if_needed_generic(); @@ -159,7 +199,7 @@ __interpolation(method, verbose) -> // or optionally __prepare_path_if_needed, if path is inefficient to compute point by point global_interpolator = if ( method == 'linear', '__interpolator_linear', - method == 'cr', '__interpolator_cr', + method == 'catmull_rom', '__interpolator_cr', method == 'gauss', _(s, p) -> __interpolator_gauB(s, p, 0), method ~ '^gauss_', ( @@ -167,13 +207,12 @@ __interpolation(method, verbose) -> type = replace(type,'_','.'); variance = round(60*number(type)); _(s, p, outer(variance)) -> __interpolator_gauB(s, p, variance); - ), - exit('Choose one of the following methods: linear, gauss, gauss_, cr') + ) ); __update(); if(verbose, 'Interpolation changed to '+method, ''); ); -__interpolation('cr', false); +__interpolation('catmull_rom', false); // adds a point to the end of the path with delay in seconds add(delay) -> @@ -216,9 +255,6 @@ repeat(times, last_section_delay) -> stretch(percentage) -> ( if (err = __is_not_valid_for_motion(), exit(err)); - if (percentage < 25 || percentage > 400, - exit('path speed can only be speed, or slowed down 4 times. Re-call command for larger changes') - ); ratio = percentage/100; previous_path_length = global_points:(-1):1; for(global_points, _:1 = _:1*ratio ); @@ -660,4 +696,4 @@ __on_close() -> ( global_playing_path = false; global_showing_path = false; -); +); \ No newline at end of file diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index bcbf958c..1662b4f9 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -24,6 +24,7 @@ "ServerPlayNetworkHandler_interactionUpdatesMixin", "ServerMetadata_motdMixin", + "MinecraftServer_pingPlayerSampleLimit", "MinecraftServer_coreMixin", @@ -175,6 +176,7 @@ "PointOfInterest_scarpetMixin", "ServerWorld_scarpetMixin", "HungerManagerMixin_scarpetEventsMixin", + "ReloadCommand_reloadAppsMixin", "RecipeManager_scarpetMixin", "Ingredient_scarpetMixin",