From 210ad834f6dc3eba49c21f5f03117e6d247db924 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 29 Nov 2020 15:44:51 +0100 Subject: [PATCH 1/8] Allow loading apps from a global path --- .../carpet/script/CarpetScriptServer.java | 69 ++++++++++++------- .../carpet/script/bundled/FileModule.java | 9 +-- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 69fd0327..4bab3309 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -16,19 +16,24 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.tree.CommandNode; + +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.WorldSavePath; import net.minecraft.util.math.BlockPos; -import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -127,20 +132,30 @@ public class CarpetScriptServer public Module getModule(String name, boolean allowLibraries) { - File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile(); - File[] listOfFiles = folder.listFiles(); - if (listOfFiles != null) - for (File script : listOfFiles) - { - if (script.getName().equalsIgnoreCase(name+".sc")) - { - return new FileModule(script); - } - if (allowLibraries && script.getName().equalsIgnoreCase(name+".scl")) - { - return new FileModule(script); - } - } + try { + Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); + Files.createDirectories(folder); + Optional + scriptPath = Files.walk(folder) + .filter(script -> + script.getFileName().toString().equalsIgnoreCase(name+".sc") || + (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl")) + ).findFirst(); + if (scriptPath.isPresent()) + return new FileModule(scriptPath.get()); + + Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); + Files.createDirectories(globalFolder); + scriptPath = Files.walk(globalFolder) + .filter(script -> + script.getFileName().toString().equalsIgnoreCase(name+".sc") || + (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl")) + ).findFirst(); + if (scriptPath.isPresent()) + return new FileModule(scriptPath.get()); + } catch (IOException e) { + CarpetSettings.LOG.error("Exception while loading the app: ", e); + } for (Module moduleData : bundledModuleData) { if (moduleData.getName().equalsIgnoreCase(name) && (allowLibraries || !moduleData.isLibrary())) @@ -173,17 +188,23 @@ public class CarpetScriptServer if (!mi.isLibrary() && !mi.getName().endsWith("_beta")) moduleNames.add(mi.getName()); } } - File folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts").toFile(); - File[] listOfFiles = folder.listFiles(); - if (listOfFiles == null) - return moduleNames; - for (File script : listOfFiles) - { - if (script.getName().endsWith(".sc")) + try { + Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); + Files.createDirectories(worldScripts); + Files.walk(worldScripts) + .filter(f -> f.toString().endsWith(".sc")) + .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT))); + + if (includeBuiltIns) { - String name = script.getName().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT); - moduleNames.add(name); + Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); + Files.createDirectories(globalScripts); + Files.walk(globalScripts) + .filter(f -> f.toString().endsWith(".sc")) + .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT))); } + } catch (IOException e) { + CarpetSettings.LOG.error("Exception while searching for apps: ", e); } return moduleNames; } diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index 031c862c..cacc2e12 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -16,6 +16,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.List; @@ -26,13 +27,13 @@ public class FileModule extends Module private String name; private String code; private boolean library; - public FileModule(File sourceFile) + public FileModule(Path sourcePath) { - library = sourceFile.getName().endsWith(".scl"); + library = sourcePath.getFileName().toString().endsWith(".scl"); try { - name = sourceFile.getName().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT); - code = new String(Files.readAllBytes(sourceFile.toPath()), StandardCharsets.UTF_8); + name = sourcePath.getFileName().toString().replaceFirst("\\.scl?","").toLowerCase(Locale.ROOT); + code = new String(Files.readAllBytes(sourcePath), StandardCharsets.UTF_8); } catch ( IOException e) { From 5dfd956ba2d0aa570f666b8e6f006c9c2de84a8e Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 29 Nov 2020 21:45:53 +0100 Subject: [PATCH 2/8] Only allow globals in clients, fix potential issue and mention in docs --- docs/scarpet/api/Overview.md | 5 ++-- docs/scarpet/api/ScriptCommand.md | 12 ++++---- docs/scarpet/language/Overview.md | 7 +++-- .../carpet/script/CarpetScriptServer.java | 29 ++++++++++--------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 9e9fc5bd..1beac900 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -6,7 +6,8 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript ## App structure The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts` -folder. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope +folder. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world. +When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the `__config()` function, if present, which will be executed once. Loading the app will also bind specific events to the event system (check Events section for details). @@ -40,7 +41,7 @@ running anything in the global scope for a `'player'` scoped app is not recommen stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. This is to allow to store multiple apps on the server/world and selectively decide which one should be running at startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called -statically, will be performed once anyways. +statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded. * `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function, all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type will be inferred from their names, otherwise diff --git a/docs/scarpet/api/ScriptCommand.md b/docs/scarpet/api/ScriptCommand.md index b10b5dc6..f2f2aa6a 100644 --- a/docs/scarpet/api/ScriptCommand.md +++ b/docs/scarpet/api/ScriptCommand.md @@ -8,13 +8,15 @@ to simulate running commands in a different scope. # `/script load / unload (global?)`, `/script in ` commands -`load / unload` commands allow for very conventient way of writing your code, providing it to the game and -distribute with your worlds without the need of use of commandblocks. Just place your scarpet code in the -/scripts folder of your world files and make sure it ends with `.sc` extension. The good thing about editing that -code is that you can no only use normal editing without the need of marking of newlines, +`load / unload` commands allow for very convenient way of writing your code, providing it to the game and +distribute with your worlds without the need of use of commandblocks. Just place your Scarpet code in the +`/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can +also save your scripts in `.minecraft/config/carpet/scripts` to make them available in any world. + +The good thing about editing that code is that you can not only use normal editing without the need of marking of newlines, but you can also use comments in your code. -a comment is anything that starts with a double slash, and continues to the end of the line: +A comment is anything that starts with a double slash, and continues to the end of the line:
 foo = 1;
diff --git a/docs/scarpet/language/Overview.md b/docs/scarpet/language/Overview.md
index 64c3d81d..0030ad73 100644
--- a/docs/scarpet/language/Overview.md
+++ b/docs/scarpet/language/Overview.md
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
 ## Code delivery, line indicators
 
 Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing 
-your code in apps (files with `.sc` extension that can be placed inside "/scripts" folder in the world files and 
-loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only 
-contain code, no need to start with "/script run" prefix
+your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files 
+or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded 
+as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only 
+contain code, no need to start with `/script run` prefix.
 
 The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder
 
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 4bab3309..e450460b 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -17,6 +17,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 import com.mojang.brigadier.exceptions.CommandSyntaxException;
 import com.mojang.brigadier.tree.CommandNode;
 
+import net.fabricmc.api.EnvType;
 import net.fabricmc.loader.api.FabricLoader;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.command.ServerCommandSource;
@@ -143,16 +144,18 @@ public class CarpetScriptServer
                 ).findFirst();
             if (scriptPath.isPresent())
                 return new FileModule(scriptPath.get());
-            
-            Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
-            Files.createDirectories(globalFolder);
-            scriptPath = Files.walk(globalFolder)
-                .filter(script -> 
-                    script.getFileName().toString().equalsIgnoreCase(name+".sc") || 
-                    (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl"))
-                ).findFirst();
-            if (scriptPath.isPresent())
-                return new FileModule(scriptPath.get());
+
+            if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT)
+            {
+                Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
+                Files.createDirectories(globalFolder);
+                scriptPath = Files.walk(globalFolder)
+                        .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
+                                (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
+                        .findFirst();
+                if (scriptPath.isPresent())
+                    return new FileModule(scriptPath.get());
+            }
         } catch (IOException e) {
             CarpetSettings.LOG.error("Exception while loading the app: ", e);
         }
@@ -193,15 +196,15 @@ public class CarpetScriptServer
             Files.createDirectories(worldScripts);
             Files.walk(worldScripts)
                 .filter(f -> f.toString().endsWith(".sc"))
-                .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT)));
+                .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
 
-            if (includeBuiltIns)
+            if (includeBuiltIns && (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT))
             {
                 Path globalScripts = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
                 Files.createDirectories(globalScripts);
                 Files.walk(globalScripts)
                     .filter(f -> f.toString().endsWith(".sc"))
-                    .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc","").toLowerCase(Locale.ROOT)));
+                    .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT)));
             }
         } catch (IOException e) {
             CarpetSettings.LOG.error("Exception while searching for apps: ", e);

From 8ab0cb4e5a9f1f15d60f6ef40697ee6d6b3e08a9 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 29 Nov 2020 18:02:38 -0400
Subject: [PATCH 3/8] fake players report 'some' ip. Fixes #403

---
 src/main/java/carpet/patches/EntityPlayerMPFake.java | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/main/java/carpet/patches/EntityPlayerMPFake.java b/src/main/java/carpet/patches/EntityPlayerMPFake.java
index 357d28c8..9befa9db 100644
--- a/src/main/java/carpet/patches/EntityPlayerMPFake.java
+++ b/src/main/java/carpet/patches/EntityPlayerMPFake.java
@@ -122,4 +122,10 @@ public class EntityPlayerMPFake extends ServerPlayerEntity
         this.hungerManager = new HungerManager();
         kill();
     }
+
+    @Override
+    public String getIp()
+    {
+        return "127.0.0.1";
+    }
 }

From 3072b1ddad9653ed6668fc89a32590dd5a0566a2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Sun, 29 Nov 2020 22:17:25 +0000
Subject: [PATCH 4/8] Merge docs for 'Merge pull request #590 from
 altrisi/global-apps

Allow loading apps from a global path'
---
 docs/scarpet/Full.md | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 9ff30fca..05a0f0d3 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -151,9 +151,10 @@ have memory and act like objects so to speak. Check `outer(var)` for details.
 ## Code delivery, line indicators
 
 Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing 
-your code in apps (files with `.sc` extension that can be placed inside "/scripts" folder in the world files and 
-loaded as a scarpet app with command `/script load [app_name]`. Scarpet apps loaded from disk should only 
-contain code, no need to start with "/script run" prefix
+your code in apps (files with `.sc` extension that can be placed inside `/scripts` folder in the world files 
+or as a globally available app in singleplayer in the `.minecraft/config/carpet/scripts` folder and loaded 
+as a Scarpet app with the command `/script load [app_name]`. Scarpet apps loaded from disk should only 
+contain code, no need to start with `/script run` prefix.
 
 The following is the code that could be provided in a `foo.sc` app file located in world `/scripts` folder
 
@@ -1705,7 +1706,8 @@ Here is the gist of the Minecraft related functions. Otherwise the CarpetScript
 ## App structure
 
 The main delivery method for scarpet programs into the game is in the form of apps in `*.sc` files located in the world `scripts` 
-folder. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope
+folder. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world. 
+When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope
 (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the
 `__config()` function, if present, which will be executed once. Loading the app will also bind specific 
 events to the event system (check Events section for details).
@@ -1739,7 +1741,7 @@ running anything in the global scope for a `'player'` scoped app is not recommen
 stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. 
 This is to allow to store multiple apps on the server/world and selectively decide which one should be running at 
 startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called 
-statically, will be performed once anyways.
+statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded.
 *   `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function,
 all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type
 will be inferred from their names, otherwise
@@ -4795,13 +4797,15 @@ to simulate running commands in a different scope.
 
 # `/script load / unload  (global?)`, `/script in ` commands
 
-`load / unload` commands allow for very conventient way of writing your code, providing it to the game and 
-distribute with your worlds without the need of use of commandblocks. Just place your scarpet code in the 
-/scripts folder of your world files and make sure it ends with `.sc` extension. The good thing about editing that 
-code is that you can no only use normal editing without the need of marking of newlines, 
+`load / unload` commands allow for very convenient way of writing your code, providing it to the game and 
+distribute with your worlds without the need of use of commandblocks. Just place your Scarpet code in the 
+`/scripts` folder of your world files and make sure it ends with `.sc` extension. In singleplayer, you can 
+also save your scripts in `.minecraft/config/carpet/scripts` to make them available in any world.
+
+The good thing about editing that code is that you can not only use normal editing without the need of marking of newlines,  
 but you can also use comments in your code.
 
-a comment is anything that starts with a double slash, and continues to the end of the line:
+A comment is anything that starts with a double slash, and continues to the end of the line:
 
 
 foo = 1;

From d980ab6d915b98d2bc98eee9c585bfdd5197bcd6 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 29 Nov 2020 19:33:28 -0400
Subject: [PATCH 5/8] added blockpredicate argument support

---
 docs/scarpet/api/Overview.md                    | 14 ++++++++++++++
 .../assets/carpet/scripts/draw_beta.sc          | 17 ++++++++++++++---
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md
index 1beac900..f81da3a5 100644
--- a/docs/scarpet/api/Overview.md
+++ b/docs/scarpet/api/Overview.md
@@ -206,6 +206,20 @@ Here is a list of built-in types, with their return value formats, as well as a
   * `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position 
   to be loaded.
   * `'block'`: a valid block state wrapped in a block value (including block properties and data)
+  * `'blockpredicate`': returns a 4-tuple indicating conditions of a block to match: block name, block tag,
+  map of required state properties, and tag to match. Either block name or block tag are `null` but not both.
+  Property map is always specified, but its empty for no conditions, and matching nbt tag can be `null` indicating
+  no requirements. Technically the 'all-matching' predicate would be `[null, null, {}, null]`, but 
+  block name or block tag is always specified. One can use the following routine to match a block agains this predicate:
+  ```
+    block_to_match = block(x,y,z);
+    [block_name, block_tag, properties, nbt_tag] = block_predicate;
+   
+    (block_name == null || block_name == block_to_match) &&
+    (block_tag == null || block_tags(block_to_match, block_tag)) &&
+    all(properties, block_state(block_to_match, _) == properties:_) &&
+    (!tag || tag_matches(block_data(block_to_match), tag))
+  ```
   * `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors.
   * `'columnpos'`: a pair of x and z coordinates.
   * `'dimension'`: string representing a valid dimension in the world.
diff --git a/src/main/resources/assets/carpet/scripts/draw_beta.sc b/src/main/resources/assets/carpet/scripts/draw_beta.sc
index ff819431..c88739d1 100644
--- a/src/main/resources/assets/carpet/scripts/draw_beta.sc
+++ b/src/main/resources/assets/carpet/scripts/draw_beta.sc
@@ -20,7 +20,7 @@ __config() -> {
     'arguments'->{
         'center'->{'type'->'pos', 'loaded'->'true'},
         'radius'->{'type'->'int', 'suggest'->[], 'min'->0},//to avoid default suggestions
-        'replacement'->{'type'->'block'},
+        'replacement'->{'type'->'blockpredicate'},
         'height'->{'type'->'int', 'suggest'->[],'min'->0},
         'orientation'->{'type'->'term', 'suggest'->['x','y','z']},
         'pointing'->{'type'->'term','suggest'->['up','down']},
@@ -32,9 +32,20 @@ __config() -> {
 
 //"Boilerplate" code
 
+_block_matches(existing, block_predicate) ->
+(
+    [name, block_tag, properties, nbt] = block_predicate;
+
+    (name == null || name == existing) &&
+    (block_tag == null || block_tags(existing, block_tag)) &&
+    all(properties, block_state(existing, _) == properties:_) &&
+    (!tag || tag_matches(block_data(existing), tag))
+);
+
 set_block(x, y, z, block, replacement)-> (
-    if(block != block(x, y, z),
-        without_updates( if (set([x, y, z],block),
+    existing = block(x, y, z);
+    if(block != existing && (replacement && _block_matches(existing, replacement) ),
+        without_updates( if (set(existing,block),
             global_affected += [x, y, z];
         ));
     );

From e603366d47d8674b98e12c9c7ee08c4cd01b02b7 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Sun, 29 Nov 2020 23:35:12 +0000
Subject: [PATCH 6/8] Merge docs for 'added blockpredicate argument support'

---
 docs/scarpet/Full.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 05a0f0d3..9478e09e 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -1906,6 +1906,20 @@ Here is a list of built-in types, with their return value formats, as well as a
   * `'pos'`: block position as a triple of coordinates. Customized with `'loaded'`, if true requiring the position 
   to be loaded.
   * `'block'`: a valid block state wrapped in a block value (including block properties and data)
+  * `'blockpredicate`': returns a 4-tuple indicating conditions of a block to match: block name, block tag,
+  map of required state properties, and tag to match. Either block name or block tag are `null` but not both.
+  Property map is always specified, but its empty for no conditions, and matching nbt tag can be `null` indicating
+  no requirements. Technically the 'all-matching' predicate would be `[null, null, {}, null]`, but 
+  block name or block tag is always specified. One can use the following routine to match a block agains this predicate:
+  ```
+    block_to_match = block(x,y,z);
+    [block_name, block_tag, properties, nbt_tag] = block_predicate;
+   
+    (block_name == null || block_name == block_to_match) &&
+    (block_tag == null || block_tags(block_to_match, block_tag)) &&
+    all(properties, block_state(block_to_match, _) == properties:_) &&
+    (!tag || tag_matches(block_data(block_to_match), tag))
+  ```
   * `'teamcolor'`: name of a team, and an integer color value of one of 16 valid team colors.
   * `'columnpos'`: a pair of x and z coordinates.
   * `'dimension'`: string representing a valid dimension in the world.

From 4bf0a2a08871335f18b13ace14d23de2d6c40f29 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Sun, 29 Nov 2020 23:57:02 -0400
Subject: [PATCH 7/8] work on distance_beta

---
 .../script/command/CommandArgument.java       |   9 +-
 .../assets/carpet/scripts/distance_beta.sc    | 151 ++++++++++++------
 2 files changed, 103 insertions(+), 57 deletions(-)

diff --git a/src/main/java/carpet/script/command/CommandArgument.java b/src/main/java/carpet/script/command/CommandArgument.java
index 1ddede7f..d2ea2f7d 100644
--- a/src/main/java/carpet/script/command/CommandArgument.java
+++ b/src/main/java/carpet/script/command/CommandArgument.java
@@ -63,6 +63,7 @@ import net.minecraft.command.argument.RotationArgumentType;
 import net.minecraft.command.argument.ScoreHolderArgumentType;
 import net.minecraft.command.argument.ScoreboardSlotArgumentType;
 import net.minecraft.command.argument.SwizzleArgumentType;
+import net.minecraft.command.argument.TeamArgumentType;
 import net.minecraft.command.argument.TimeArgumentType;
 import net.minecraft.command.argument.UuidArgumentType;
 import net.minecraft.command.argument.Vec2ArgumentType;
@@ -130,7 +131,6 @@ public abstract class CommandArgument
             new VanillaUnconfigurableArgument( "blockpredicate", BlockPredicateArgumentType::blockPredicate,
                     (c, p) -> ValueConversions.ofBlockPredicate(c.getSource().getMinecraftServer().getTagManager(), BlockPredicateArgumentType.getBlockPredicate(c, p)), false
             ),
-            // block_predicate todo - not sure about the returned format. Needs to match block tags used in the API (future)
             new VanillaUnconfigurableArgument("teamcolor", ColorArgumentType::color,
                     (c, p) -> {
                         Formatting format = ColorArgumentType.getColor(c, p);
@@ -165,7 +165,6 @@ public abstract class CommandArgument
                     (c, p) -> ValueConversions.of(Registry.ENCHANTMENT.getId(ItemEnchantmentArgumentType.getEnchantment(c, p))), false
             ),
             // item_predicate  ?? //same as item but accepts tags, not sure right now
-            // slot // item_slot
             new SlotArgument(),
             new VanillaUnconfigurableArgument("item", ItemStackArgumentType::itemStack,
                     (c, p) -> ValueConversions.of(ItemStackArgumentType.getItemStackArgument(c, p).createStack(1, false)), false
@@ -241,7 +240,9 @@ public abstract class CommandArgument
             new VanillaUnconfigurableArgument("swizzle", SwizzleArgumentType::swizzle,
                     (c, p) -> StringValue.of(SwizzleArgumentType.getSwizzle(c, p).stream().map(Direction.Axis::asString).collect(Collectors.joining())), true
             ),
-            // team
+            new VanillaUnconfigurableArgument("team", TeamArgumentType::team,
+                    (c, p) -> StringValue.of(TeamArgumentType.getTeam(c, p).getName()), false
+            ),
             new VanillaUnconfigurableArgument("time", TimeArgumentType::time,
                     (c, p) -> new NumericValue(IntegerArgumentType.getInteger(c, p)), false
             ),
@@ -437,7 +438,7 @@ public abstract class CommandArgument
             if (config.containsKey("options"))
             {
                 Value optionsValue = config.get("options");
-                if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom sting type requires options passed as a list");
+                if (!(optionsValue instanceof ListValue)) throw new InternalExpressionException("Custom string type requires options passed as a list");
                 validOptions = ((ListValue) optionsValue).getItems().stream()
                         .map(v -> caseSensitive?v.getString():(v.getString().toLowerCase(Locale.ROOT)))
                         .collect(Collectors.toSet());
diff --git a/src/main/resources/assets/carpet/scripts/distance_beta.sc b/src/main/resources/assets/carpet/scripts/distance_beta.sc
index b1b3bc6d..f1c8409a 100644
--- a/src/main/resources/assets/carpet/scripts/distance_beta.sc
+++ b/src/main/resources/assets/carpet/scripts/distance_beta.sc
@@ -1,91 +1,136 @@
 import('math','_euclidean','_manhattan','_round');
 
+global_display_modes = ['none', 'line', 'centered_sphere', 'box'];
+
 __config()->{
     'commands'->{
         'from '->'set_start',
-        'from  to '->'_calculate',
-        'to '->_(end_pos)->(_calculate(null,end_pos)),
-        'toggle_line'->'_toggle_line'
+        'from  to '-> _(start, end) -> (global_current_start = start; calculate(end)),
+        'to '-> 'calculate',
+        'clear' -> _() -> global_display_shapes = [],
+        'clear last' -> _() -> if(global_display_shapes, delete(global_display_shapes, -1)),
+        'display ' -> 'set_mode'
     },
     'arguments' -> {
         'start' -> {'type' -> 'location'},
-        'end' -> {'type' -> 'location'}
+        'end' -> {'type' -> 'location'},
+        'mode' -> {'type' -> 'term', 'options' -> global_display_modes }
     }
 };
 
-global_start=null;
-global_end=null;
+global_current_start = null;
+global_display_shapes = [];
+global_renderer_running = false;
 
-global_show_line=false;
+
+global_current_mode = 'none';
+global_generators = {
+    'line' -> _(from, to) -> [
+        ['line',20,'from',from,'to',to],
+        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+    ],
+    'centered_sphere' -> _(from, to) -> [
+        ['line',20,'from',from,'to',to, 'line', 5, 'color', 0x88888888],
+        ['sphere',20,'center',from,'radius',_euclidean(from,to)],
+        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+    ],
+    'box' -> _(from, to) -> [
+        ['line',20,'from',from,'to',to, 'line', 5, 'color', 0x88888888],
+        ['box',20,'from',from,'to',to],
+        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+    ],
+};
 
 __mktp(pos) -> str('!/tp %.6f %.6f %.6f', pos);
 
 set_start(pos)->(
-    global_start = pos;
-    global_end = null;
+    global_current_start = pos;
     print(player(),format('gi Initial point set to: ', 'g '+pos, __mktp(pos)))
 );
 
-_toggle_line()->(
-    global_show_line= !global_show_line;
-    if(global_show_line,
-        print('Toggled lines on');
-        _draw_line()
-    , // else
-        print('Toggled lines off')
-    );
-);
-
-_calculate(start_pos, end_pos)->(
-    if(start_pos!=null,
-        set_start(start_pos);
-        calculate(end_pos)
-    , // else
-        if(global_start,
-            calculate(end_pos),
-            print(player(),format('r There was no initial point for '+player()));
-            set_start(end_pos)
-        )
+set_mode(mode)->(
+    if (global_display_modes ~ mode,
+        global_current_mode = mode
     )
 );
 
-__on_player_places_block(player, item_tuple, hand, block) ->(
-    if(block=='brown_carpet' && system_info('world_carpet_rules'):'carpets'=='true',
+calculate(end)->(
+    if(global_current_start != null,
+        measure(end)
+    , // else
+        print(player(),format('r No initial point selected'));
+    )
+);
+
+_carpets() -> system_info('world_carpet_rules'):'carpets'=='true';
+
+
+__on_player_places_block(player, item_tuple, hand, block) ->
+(
+    if(block=='brown_carpet' && _carpets(),
         effective_pos = pos(block)+[0.5, 0, 0.5];
-        if(global_start==null||player~'sneaking',//wont complain for first carpet
-            set_start(effective_pos),
-            _calculate(null, effective_pos)
+        if(global_current_start==null||player~'sneaking',//wont complain for first carpet
+            set_start(effective_pos)
+        , // else
+            calculate(effective_pos)
         )
     )
 );
 
-calculate(end_pos)->(
-    global_end = end_pos;
-    manhattan = _round(_manhattan(global_start, global_end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places
-    spherical = _round(_euclidean(global_start, global_end), 0.001);
+__on_player_uses_item(player, item_tuple, hand) ->
+(
+    if (item_tuple:0 == 'brown_carpet' && hand == 'mainhand' && _carpets(),
+       if (player ~'sneaking',
+        global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1);
+        display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode));
+      ,
+         if(global_display_shapes, delete(global_display_shapes, -1))
+      )
+    )
+);
+
+measure(end)->
+(
+    if (global_current_start == null, return());
+    start = global_current_start;
+    manhattan = _round(_manhattan(start, end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places
+    spherical = _round(_euclidean(start, end), 0.001);
     //useful for chunk distances etc.
-    cylindrical = _round(_euclidean(global_start, [global_end:0, global_start:1, global_end:2]), 0.001);
-    
+    cylindrical = _round(_euclidean(start, [end:0, start:1, end:2]), 0.001);
 
     print(player(),format(
         'w Distance between ',
-        'c '+str('[%.1f, %.1f, %.1f]', global_start), __mktp(global_start),
+        'c '+str('[%.1f, %.1f, %.1f]', start), __mktp(start),
         'w  and ',
-        'c '+str('[%.1f, %.1f, %.1f]', global_end), __mktp(global_end), 'w :\n',
+        'c '+str('[%.1f, %.1f, %.1f]', end), __mktp(end), 'w :\n',
         'w  - Spherical: ', 'wb '+spherical+'\n',
         'w  - Cylindrical: ', 'wb '+cylindrical+'\n',
         'w  - Manhattan: ', 'wb '+manhattan
     ));
-
-    schedule(0,'_draw_line');
-);
-
-_draw_line()->(
-    if(global_show_line,//It will always draw from the global positions, so it will turn on if there is a line
-        if ( global_start && global_end,
-            draw_shape('line',20,'from',global_start,'to',global_end, 'player',player());
-            draw_shape('label',20,'pos',global_end,'text',_round(_euclidean(global_start,global_end),0.001));
-        );
-        schedule(20,'_draw_line')
+    if (global_current_mode != 'none',
+        global_display_shapes += _create_shapes(global_current_mode, start, end);
+        for (global_display_shapes, draw_shape(_) );
+        if (!global_renderer_running,
+            global_renderer_running = true;
+            render()
+        )
     )
 );
+
+_create_shapes(mode, from, to) ->
+(
+
+    shapes = call(global_generators:mode, from+[0, 0.07, 0], to+[0, 0.07, 0]);
+    map(shapes, put(_, null, ['player', player()], 'extend'); _);
+);
+
+render()->(
+    if (!global_display_shapes,
+        global_renderer_running = false;
+        return();
+    );
+    for (global_display_shapes,
+        draw_shape(_);
+    );
+    schedule(20,'render')
+);

From 9bdc0c31e461d52c9186ba3f00e5293e9e23194e Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Mon, 30 Nov 2020 01:27:08 -0400
Subject: [PATCH 8/8] work on distance_beta

---
 .../assets/carpet/scripts/distance_beta.sc    | 96 ++++++++++++-------
 1 file changed, 59 insertions(+), 37 deletions(-)

diff --git a/src/main/resources/assets/carpet/scripts/distance_beta.sc b/src/main/resources/assets/carpet/scripts/distance_beta.sc
index f1c8409a..1865885a 100644
--- a/src/main/resources/assets/carpet/scripts/distance_beta.sc
+++ b/src/main/resources/assets/carpet/scripts/distance_beta.sc
@@ -1,6 +1,6 @@
 import('math','_euclidean','_manhattan','_round');
 
-global_display_modes = ['none', 'line', 'centered_sphere', 'box'];
+global_display_modes = ['chat', 'line', 'centered_sphere', 'box'];
 
 __config()->{
     'commands'->{
@@ -9,38 +9,66 @@ __config()->{
         'to '-> 'calculate',
         'clear' -> _() -> global_display_shapes = [],
         'clear last' -> _() -> if(global_display_shapes, delete(global_display_shapes, -1)),
-        'display ' -> 'set_mode'
+        'display ' -> 'set_mode',
+        'assist ' -> 'set_assist',
+        'assist none' -> _() -> set_assist(null),
     },
     'arguments' -> {
         'start' -> {'type' -> 'location'},
         'end' -> {'type' -> 'location'},
-        'mode' -> {'type' -> 'term', 'options' -> global_display_modes }
+        'mode' -> {'type' -> 'term', 'options' -> global_display_modes },
+        'assist' -> {'type' -> 'block', 'suggest' -> ['brown_carpet']},
     }
 };
 
 global_current_start = null;
 global_display_shapes = [];
 global_renderer_running = false;
+global_current_mode = 'chat';
 
+global_assist_block = null;
+
+set_assist(block) ->
+(
+
+   if (block == null,
+      global_assist_block = null;
+      handle_event('player_places_block', null);
+      handle_event('player_uses_item', null);
+
+   ,
+      global_assist_block = str(block);
+      handle_event('player_places_block', 'on_player_places_block');
+      handle_event('player_uses_item', 'on_player_uses_item');
+   )
+);
 
-global_current_mode = 'none';
 global_generators = {
     'line' -> _(from, to) -> [
-        ['line',20,'from',from,'to',to],
-        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+        ['line', 'from',from,'to',to]
     ],
     'centered_sphere' -> _(from, to) -> [
-        ['line',20,'from',from,'to',to, 'line', 5, 'color', 0x88888888],
-        ['sphere',20,'center',from,'radius',_euclidean(from,to)],
-        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+        ['line', 'from',from,'to',to, 'line', 5, 'color', 0x88888888],
+        ['sphere', 'center',from,'radius',_euclidean(from,to)]
     ],
     'box' -> _(from, to) -> [
-        ['line',20,'from',from,'to',to, 'line', 5, 'color', 0x88888888],
-        ['box',20,'from',from,'to',to],
-        ['label',20,'pos',to+[0,0.2,0],'text',_round(_euclidean(from,to),0.001)]
+        ['line', 'from',from,'to',to, 'line', 5, 'color', 0x88888888],
+        ['box', 'from',from,'to',to]
     ],
 };
 
+_create_shapes(mode, from, to) ->
+(
+    shapes = call(global_generators:mode, from+[0, 0.07, 0], to+[0, 0.07, 0]);
+    shapes += ['label', 'pos',to+[0,0.2,0],             'align', 'right', 'indent', -1.5, 'text',format('rb Cylindrical:')];
+    shapes += ['label', 'pos',to+[0,0.2,0],             'align', 'left',                  'text',_round(_euclidean(from, [to:0, from:1, to:2]), 0.001)];
+    shapes += ['label', 'pos',to+[0,0.2,0], 'height', 1, 'align', 'right', 'indent', -1.5, 'text',format('rb Manhattan:')];
+    shapes += ['label', 'pos',to+[0,0.2,0], 'height', 1, 'align', 'left',                  'text',_round(_manhattan(from,to),0.001)];
+    shapes += ['label', 'pos',to+[0,0.2,0], 'height', 2, 'align', 'right', 'indent', -1.5, 'text',format('rb Euclidian:')];
+    shapes += ['label', 'pos',to+[0,0.2,0], 'height', 2, 'align', 'left',                  'text',_round(_euclidean(from,to),0.001)];
+    map(shapes, put(_, 1, [20, 'player', player()], 'extend'); _);
+);
+
 __mktp(pos) -> str('!/tp %.6f %.6f %.6f', pos);
 
 set_start(pos)->(
@@ -65,9 +93,9 @@ calculate(end)->(
 _carpets() -> system_info('world_carpet_rules'):'carpets'=='true';
 
 
-__on_player_places_block(player, item_tuple, hand, block) ->
+on_player_places_block(player, item_tuple, hand, block) ->
 (
-    if(block=='brown_carpet' && _carpets(),
+    if(block==global_assist_block && _carpets(),
         effective_pos = pos(block)+[0.5, 0, 0.5];
         if(global_current_start==null||player~'sneaking',//wont complain for first carpet
             set_start(effective_pos)
@@ -77,9 +105,9 @@ __on_player_places_block(player, item_tuple, hand, block) ->
     )
 );
 
-__on_player_uses_item(player, item_tuple, hand) ->
+on_player_uses_item(player, item_tuple, hand) ->
 (
-    if (item_tuple:0 == 'brown_carpet' && hand == 'mainhand' && _carpets(),
+    if (item_tuple:0 == global_assist_block && hand == 'mainhand' && _carpets(),
        if (player ~'sneaking',
         global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1);
         display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode));
@@ -93,21 +121,22 @@ measure(end)->
 (
     if (global_current_start == null, return());
     start = global_current_start;
-    manhattan = _round(_manhattan(start, end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places
-    spherical = _round(_euclidean(start, end), 0.001);
-    //useful for chunk distances etc.
-    cylindrical = _round(_euclidean(start, [end:0, start:1, end:2]), 0.001);
+    if (global_current_mode == 'chat',
+        manhattan = _round(_manhattan(start, end),0.001);//rounding to nearest 0.01 for precision, but not ridiculous decimal places
+        spherical = _round(_euclidean(start, end), 0.001);
+        //useful for chunk distances etc.
+        cylindrical = _round(_euclidean(start, [end:0, start:1, end:2]), 0.001);
 
-    print(player(),format(
-        'w Distance between ',
-        'c '+str('[%.1f, %.1f, %.1f]', start), __mktp(start),
-        'w  and ',
-        'c '+str('[%.1f, %.1f, %.1f]', end), __mktp(end), 'w :\n',
-        'w  - Spherical: ', 'wb '+spherical+'\n',
-        'w  - Cylindrical: ', 'wb '+cylindrical+'\n',
-        'w  - Manhattan: ', 'wb '+manhattan
-    ));
-    if (global_current_mode != 'none',
+        print(player(),format(
+            'w Distance between ',
+            'c '+str('[%.1f, %.1f, %.1f]', start), __mktp(start),
+            'w  and ',
+            'c '+str('[%.1f, %.1f, %.1f]', end), __mktp(end), 'w :\n',
+            'w  - Spherical: ', 'wb '+spherical+'\n',
+            'w  - Cylindrical: ', 'wb '+cylindrical+'\n',
+            'w  - Manhattan: ', 'wb '+manhattan
+        ));
+    , // else
         global_display_shapes += _create_shapes(global_current_mode, start, end);
         for (global_display_shapes, draw_shape(_) );
         if (!global_renderer_running,
@@ -117,13 +146,6 @@ measure(end)->
     )
 );
 
-_create_shapes(mode, from, to) ->
-(
-
-    shapes = call(global_generators:mode, from+[0, 0.07, 0], to+[0, 0.07, 0]);
-    map(shapes, put(_, null, ['player', player()], 'extend'); _);
-);
-
 render()->(
     if (!global_display_shapes,
         global_renderer_running = false;