From 210ad834f6dc3eba49c21f5f03117e6d247db924 Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 29 Nov 2020 15:44:51 +0100 Subject: [PATCH 01/64] 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 02/64] 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 03/64] 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 04/64] 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 05/64] 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 06/64] 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 07/64] 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 08/64] 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;

From 3a2edb9ed227a1be9fc90815f7902d2ecbeccab1 Mon Sep 17 00:00:00 2001
From: replaceitem <40722305+replaceitem@users.noreply.github.com>
Date: Mon, 30 Nov 2020 16:12:04 +0100
Subject: [PATCH 09/64] Added scarpet bossbar function (#585)

---
 docs/scarpet/api/Scoreboard.md                |  59 ++++++++
 docs/scarpet/resources/editors/idea/2.txt     |   5 +-
 .../scarpet/resources/editors/npp/scarpet.xml |   2 +
 .../java/carpet/script/api/Scoreboards.java   | 131 ++++++++++++++++++
 4 files changed, 195 insertions(+), 2 deletions(-)

diff --git a/docs/scarpet/api/Scoreboard.md b/docs/scarpet/api/Scoreboard.md
index 98e9c9b7..36903292 100644
--- a/docs/scarpet/api/Scoreboard.md
+++ b/docs/scarpet/api/Scoreboard.md
@@ -104,3 +104,62 @@ team_property('admin','seeFriendlyInvisibles',true)       Make all players in 'a
 team_property('admin','deathMessageVisibility','hideForOtherTeams')       Make all players in 'admin' see other admins even when invisible
 ```
 
+## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)`
+
+Manage bossbars just like with the `/bossbar` command.
+
+Without any arguments, returns a list of all bossbars.
+
+When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar.
+Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`.
+In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically.
+If the id was invalid (for example by having more than one colon), returns `null`.
+If the bossbar already exists, returns `false`.
+
+`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified)
+
+`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified
+
+`bossbar('foo:bar:baz') => null` Invalid identifier
+
+`bossbar(id,property)` is used to query the `property` of a bossbar.
+
+`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`.
+
+Available properties are:
+
+* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'`
+
+* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'`
+
+* value: value of the bossbar progress
+
+* max: maximum value of the bossbar progress, by default 100
+
+* name: Text to display above the bossbar, supports formatted text
+
+* visible: whether the bossbar is visible or not
+
+* players: List of players that can see the bossbar
+
+* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present)
+
+* remove: remove this bossbar, no `value` required
+
+```
+bossbar('script:test','style','notched_12')
+bossbar('script:test','value',74)
+bossbar('script:test','name',format('rb Test'))  -> Change text
+bossbar('script:test','visible',false)  -> removes visibility, but keeps players
+bossbar('script:test','players',player('all'))  -> Visible for all players
+bossbar('script:test','players',player('Steve'))  -> Visible for Steve only 
+bossbar('script:test','players',null)  -> Invalid player, removing all players
+bossbar('script:test','add_player',player('Alex'))  -> Add Alex to the list of players that can see the bossbar
+bossbar('script:test','remove')  -> remove bossbar 'script:test'
+for(bossbar(),bossbar(_,'remove'))  -> remove all bossbars
+```
+
+
+
+
+
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 642a8bc5..8f826f39 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -12,6 +12,7 @@ block_tick
 block_tags
 blocks_daylight
 blocks_movement
+bossbar
 brightness
 chunk_tickets
 crafting_remaining_item
@@ -92,10 +93,10 @@ scoreboard_remove
 scoreboard_display
 tag_matches
 team_add
-team_list
-team_remove
 team_leave
+team_list
 team_property
+team_remove
 read_file
 see_sky
 set
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 65a7800d..5dba63e7 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -64,6 +64,8 @@
 
                 team_add team_list team_remove team_leave team_property
 
+                bossbar
+
                 player spawn entity_id entity_list entity_area entity_selector query modify entity_event entity_load_handler entity_types
 
                 stack_limit item_category recipe_data crafting_remaining_item
diff --git a/src/main/java/carpet/script/api/Scoreboards.java b/src/main/java/carpet/script/api/Scoreboards.java
index 0ba8d9e5..cf1c2859 100644
--- a/src/main/java/carpet/script/api/Scoreboards.java
+++ b/src/main/java/carpet/script/api/Scoreboards.java
@@ -12,6 +12,9 @@ import carpet.script.value.StringValue;
 import carpet.script.value.FormattedTextValue;
 import carpet.script.value.Value;
 import net.minecraft.entity.Entity;
+import net.minecraft.entity.boss.BossBar;
+import net.minecraft.entity.boss.BossBarManager;
+import net.minecraft.entity.boss.CommandBossBar;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.scoreboard.Scoreboard;
 import net.minecraft.scoreboard.ServerScoreboard;
@@ -20,9 +23,12 @@ import net.minecraft.scoreboard.ScoreboardObjective;
 import net.minecraft.scoreboard.ScoreboardPlayerScore;
 import net.minecraft.scoreboard.Team;
 import net.minecraft.scoreboard.AbstractTeam;
+import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.text.LiteralText;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.InvalidIdentifierException;
 
 import java.util.stream.Collectors;
 
@@ -396,6 +402,131 @@ public class Scoreboards {
             }
             return LazyValue.TRUE;
         });
+
+        expression.addLazyFunction("bossbar", -1, (c, t, lv) ->
+        {
+            BossBarManager bossBarManager = ((CarpetContext)c).s.getMinecraftServer().getBossBarManager();
+            if(lv.size() > 3) throw new InternalExpressionException("'bossbar' accepts max three arguments");
+
+            if(lv.size() == 0) {
+                Value ret = ListValue.wrap(bossBarManager.getAll().stream().map(CommandBossBar::getId).map(Identifier::toString).map(StringValue::of).collect(Collectors.toList()));
+                return (_c, _t) -> ret;
+            }
+
+            String id = lv.get(0).evalValue(c).getString();
+            Identifier identifier;
+            try {
+                identifier = new Identifier(id);
+            } catch (InvalidIdentifierException invalidIdentifierException) {
+                return LazyValue.NULL;
+            }
+
+            if(lv.size() == 1) {
+                if(bossBarManager.get(identifier) != null) return LazyValue.FALSE;
+                Value ret = StringValue.of(bossBarManager.add(identifier,new LiteralText(id)).getId().toString());
+                return (_c, _t) -> ret;
+            }
+
+            String property = lv.get(1).evalValue(c).getString();
+
+            CommandBossBar bossBar = bossBarManager.get(identifier);
+            if(bossBar == null) return LazyValue.NULL;
+
+            Value propertyValue = (lv.size() == 3)?lv.get(2).evalValue(c):null;
+
+            switch (property) {
+                case "color":
+                    if(propertyValue == null) {
+                        BossBar.Color color = (bossBar).getColor();
+                        if(color == null) return LazyValue.NULL;
+                        return (_c, _t) -> StringValue.of(color.getName());
+                    }
+
+                    BossBar.Color color = BossBar.Color.byName(propertyValue.getString());
+                    if(color == null) return LazyValue.NULL;
+                    bossBar.setColor(BossBar.Color.byName(propertyValue.getString()));
+                    return LazyValue.TRUE;
+                case "max":
+                    if(propertyValue == null) return (_c, _t) -> NumericValue.of(bossBar.getMaxValue());
+
+                    if(!(propertyValue instanceof NumericValue)) throw new InternalExpressionException("'bossbar' requires a number as the value for the property " + property);
+                    bossBar.setMaxValue(((NumericValue) propertyValue).getInt());
+                    return LazyValue.TRUE;
+                case "name":
+                    if(propertyValue == null) return (_c, _t) -> new FormattedTextValue(bossBar.getName());
+
+                    if(propertyValue instanceof FormattedTextValue) {
+                        bossBar.setName(((FormattedTextValue) propertyValue).getText());
+                    } else {
+                        bossBar.setName(new LiteralText(propertyValue.getString()));
+                    }
+                    return LazyValue.TRUE;
+                case "add_player":
+                    if(propertyValue == null) throw new InternalExpressionException("Bossbar property " + property + " can't be queried, add a third parameter");
+
+                    if(propertyValue instanceof ListValue) {
+                        ((ListValue) propertyValue).getItems().forEach((v)->{
+                            ServerPlayerEntity player = EntityValue.getPlayerByValue(((CarpetContext)c).s.getMinecraftServer(),propertyValue);
+                            if(player != null) bossBar.addPlayer(player);
+                        });
+                        return LazyValue.TRUE;
+                    }
+
+                    ServerPlayerEntity player = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), propertyValue);
+                    if(player != null) {
+                        bossBar.addPlayer(player);
+                        return LazyValue.TRUE;
+                    }
+                    return LazyValue.FALSE;
+                case "players":
+                    if(propertyValue == null) {
+                        return (_c, _t) -> ListValue.wrap(bossBar.getPlayers().stream().map(EntityValue::new).collect(Collectors.toList()));
+                    }
+
+                    if(propertyValue instanceof ListValue) {
+                        bossBar.clearPlayers();
+                        ((ListValue) propertyValue).getItems().forEach((v) -> {
+                            ServerPlayerEntity p = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), v);
+                            if (p != null) bossBar.addPlayer(p);
+                        });
+                        return LazyValue.TRUE;
+                    }
+
+
+                    ServerPlayerEntity p = EntityValue.getPlayerByValue(((CarpetContext) c).s.getMinecraftServer(), propertyValue);
+                    bossBar.clearPlayers();
+                    if (p != null) {
+                        bossBar.addPlayer(p);
+                        return LazyValue.TRUE;
+                    }
+                    return LazyValue.FALSE;
+                case "style":
+                    if(propertyValue == null) {
+                        return (_c, _t) -> StringValue.of(bossBar.getOverlay().getName());
+                    }
+
+                    BossBar.Style style = BossBar.Style.byName(propertyValue.getString());
+                    if(style == null) throw new InternalExpressionException("'" + propertyValue.getString() + "' is not a valid value for property " + property);
+                    bossBar.setOverlay(style);
+                    return LazyValue.TRUE;
+                case "value":
+                    if(propertyValue == null) return (_c, _t) -> NumericValue.of(bossBar.getValue());
+
+                    if(!(propertyValue instanceof NumericValue)) throw new InternalExpressionException("'bossbar' requires a number as the value for the property " + property);
+                    bossBar.setValue(((NumericValue) propertyValue).getInt());
+                    return LazyValue.TRUE;
+                case "visible":
+                    if(propertyValue == null) return (_c, _t) -> new NumericValue(bossBar.isVisible());
+
+                    bossBar.setVisible(propertyValue.getBoolean());
+                    return LazyValue.TRUE;
+                case "remove":
+                    bossBarManager.remove(bossBar);
+                    return LazyValue.TRUE;
+                default:
+                    throw new InternalExpressionException("Unknown bossbar property " + property);
+            }
+        });
     }
 }
 

From 926c1a8e6317d0ed117506ea4ad08a2afd650f32 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Mon, 30 Nov 2020 15:12:27 +0000
Subject: [PATCH 10/64] Merge docs for 'Added scarpet bossbar function (#585)'

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

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index 9478e09e..6471c35a 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4283,6 +4283,65 @@ team_property('admin','seeFriendlyInvisibles',true)       Make all players in 'a
 team_property('admin','deathMessageVisibility','hideForOtherTeams')       Make all players in 'admin' see other admins even when invisible
 ```
 
+## `bossbar()`, `bossbar(id)`, `bossbar(id,property,value?)`
+
+Manage bossbars just like with the `/bossbar` command.
+
+Without any arguments, returns a list of all bossbars.
+
+When an id is specified, creates a bossbar with that `id` and returns the id of the created bossbar.
+Bossbar ids need a namespace and a name. If no namespace is specified, it will automatically use `minecraft:`.
+In that case you should keep track of the bossbar with the id that `bossbar(id)` returns, because a namespace may be added automatically.
+If the id was invalid (for example by having more than one colon), returns `null`.
+If the bossbar already exists, returns `false`.
+
+`bossbar('timer') => 'minecraft:timer'` (Adds the namespace `minecraft:` because none is specified)
+
+`bossbar('scarpet:test') => 'scarpet:test'` In this case there is already a namespace specified
+
+`bossbar('foo:bar:baz') => null` Invalid identifier
+
+`bossbar(id,property)` is used to query the `property` of a bossbar.
+
+`bossbar(id,property,value)` can modify the `property` of the bossbar to a specified `value`.
+
+Available properties are:
+
+* color: can be `'pink'`, `'blue'`, `'red'`, `'green'`, `'yellow'`, `'purple'` or `'white'`
+
+* style: can be `'progress'`, `'notched_6'`, `'notched_10'`, `'notched_12'` or `'notched_20'`
+
+* value: value of the bossbar progress
+
+* max: maximum value of the bossbar progress, by default 100
+
+* name: Text to display above the bossbar, supports formatted text
+
+* visible: whether the bossbar is visible or not
+
+* players: List of players that can see the bossbar
+
+* add_player: add a player to the players that can see this bossbar, this can only be used for modifying (`value` must be present)
+
+* remove: remove this bossbar, no `value` required
+
+```
+bossbar('script:test','style','notched_12')
+bossbar('script:test','value',74)
+bossbar('script:test','name',format('rb Test'))  -> Change text
+bossbar('script:test','visible',false)  -> removes visibility, but keeps players
+bossbar('script:test','players',player('all'))  -> Visible for all players
+bossbar('script:test','players',player('Steve'))  -> Visible for Steve only 
+bossbar('script:test','players',null)  -> Invalid player, removing all players
+bossbar('script:test','add_player',player('Alex'))  -> Add Alex to the list of players that can see the bossbar
+bossbar('script:test','remove')  -> remove bossbar 'script:test'
+for(bossbar(),bossbar(_,'remove'))  -> remove all bossbars
+```
+
+
+
+
+
 # Auxiliary aspects
 
 Collection of other methods that control smaller, yet still important aspects of the game

From d29f49967f0d06e21be9959a21fee4eda74d93de Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Wed, 2 Dec 2020 01:02:36 -0400
Subject: [PATCH 11/64] rework of the event system, allowing apps to function
 more independently and allowing for player scoped apps to handle global
 events. fixes #596

---
 .gitignore                                    |   7 +-
 docs/scarpet/api/Events.md                    | 145 +++++-----------
 docs/scarpet/api/Overview.md                  |   4 +-
 src/main/java/carpet/CarpetServer.java        |  15 +-
 .../java/carpet/commands/ScriptCommand.java   |  21 +--
 .../java/carpet/script/CarpetEventServer.java | 160 ++++++++++++------
 .../java/carpet/script/CarpetScriptHost.java  |  64 +++++--
 .../carpet/script/CarpetScriptServer.java     | 136 ++++++++++-----
 .../java/carpet/script/EntityEventsGroup.java |  33 ++--
 src/main/java/carpet/script/ScriptHost.java   |  14 +-
 .../java/carpet/script/api/Auxiliary.java     |   5 +-
 .../java/carpet/script/value/EntityValue.java |   2 +-
 .../assets/carpet/scripts/distance_beta.sc    |   7 +-
 13 files changed, 367 insertions(+), 246 deletions(-)

diff --git a/.gitignore b/.gitignore
index aca7bbba..3e26bc92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,10 @@ out/
 classes/
 publish/
 
+# git
+
+.shelf/
+
 # idea
 
 .idea/
@@ -27,4 +31,5 @@ bin/
 
 # fabric
 
-run/
\ No newline at end of file
+run/
+logs/
\ No newline at end of file
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index 2f343835..e64aad1f 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -9,10 +9,8 @@ with `__on_` and has the required arguments, will be bound automatically
 of such function would result in unbinding the app from this event.
 
 In case of `player` scoped apps, 
-all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, cannot be handled
-in `'player'` scoped app.
-
-`'global'` scoped apps will receive both global and player built-in events.
+all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific
+player target will be executed multiple times, once for each player, and once in `'global'` scoped apps.
 
 Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice
 for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the
@@ -31,8 +29,8 @@ if it accepts required number of parameters.
 
 ## Built-in global events
 
-Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you
-simply wouldn't know which player to hook up this event to.
+Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance
+ is responsible independently from handling their events, so a global event may be executed multiple times for each player.
 
 ### `__on_tick()`
 Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()`
@@ -205,8 +203,11 @@ is handled before scoreboard values for these statistics are changed.
 
 App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value
 as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is
-either targetting all global context apps, if no target player has been identified, or only player scoped apps, if the target player
-is specified.
+either targetting all apps instances for all players, including global apps, if no target player has been identified, 
+or only player scoped apps, if the target player
+is specified, running once for that player app. You cannot target global apps with player-targeted signals. Built-in events
+do target global apps, since their first argument is clearly defined and passed. That may change in the future in case there is 
+a compelling argument to be able to target global apps with player scopes. 
 
 Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have
 have no idea why you would need that. The following snippets have the same effect:
@@ -230,11 +231,11 @@ handle_event('player_breaks_block', null);
 And `signal_event` can be used as a trigger, called twice for player based built-in events
 ```
 signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
-signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps
+signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps and all player instances
 ```
-or (for global only events)
+or (for global events)
 ```
-signal_event('tick') // trigger only global scoped apps with no extra arguments
+signal_event('tick') // trigger all apps with a tick event
 ```
 
 ### `handle_event(event, callback ...)`
@@ -248,6 +249,8 @@ If extra arguments are provided, they will be appended to the argument list of t
 Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event,
 or incorect number of parameters for the event).
 
+If a callback is specified as `null`, the given app (or player app instance )stops handling that event. 
+
 
 foo(a) -> print(a);
 handle_event('boohoo', 'foo');
@@ -262,111 +265,55 @@ handle_event('tick', null)  // nah, ima good, kthxbai
 
 ### `signal_event(event, target_player?, ... args?)`
 
-Fires a specific event. If the event does not exist (only `handle_event` creates missing mising new events), or provided argument list
+Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
 was not matching the callee expected arguments, returns `null`, 
 otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting
 only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event.
-If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped.
-Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover
-all app scopes. Note that all built-in player events have a player as a first argument, so to trigger these events, you need to 
+If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
+Note that all built-in player events have a player as a first argument, so to trigger these events, you need to 
 provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function.
 
 
 signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
-signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps
-signal_event('tick') // triggering tick event in all loaded apps
+signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps and all player instances
+signal_event('tick') // trigger all apps with a tick event
 
## Custom events example -The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous -squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but -in this case the event is once called as a global event, and once as a player event, targeting each app once. Custom events is -a good idea to share common event detection code with one app, and using the outcome of these events in multiple places. -Obviously you would define each event handler once not twice in your app system. - -In this -case here we have two ways of detecting all players squatting for more than 40 ticks - one of those apps will produce cloud particles -in an apprpriate location, and the other will produce a sound when it happens, and they both will proceed to signal the same event, -thankfully for different scopes, otherwise other apps would trigger twice. +The following example shows how you can communicate between different instances of the same player scoped app. It important to note +that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called +`tp_request` and is triggered with a command. + ``` -// detector1.sc -// detecting event in a tick loop -__config() -> {'scope' -> 'global'}; - -global_sneaks = {}; -__on_tick() -> for(player('all'), - if (_~'sneaking', - sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0); - if (sneak_time > 20 && sneak_time < 40, - particle('cloud', pos(_) + [0, _~'height'/2, 0] - _~'look', 1, 0.1) - ); - if (sneak_time == 40, - signal_event('player_farts', _, _) // triggering all player scoped apps from a global app +// tpa.sc +global_requester = null; +__config() -> { + 'commands' -> { + '' -> _(to) -> signal_event('tp_request', to, player()), + 'accept' -> _() -> if(global_requester, + run('tp '+global_requester~'command_name'); + global_requester = null ) - , // else - delete(global_sneaks:_) - ) -) + }, + 'arguments' -> { + 'player' -> {'type' -> 'players', 'single' -> true} + } +}; +handle_event('tp_request', _(req) -> ( + global_requester = req; + print(player(), format( + 'w '+req+' requested to teleport to you. Click ', + 'yb here', '^yb here', '!/tpa accept', + 'w to accept it.' + )); +)); ``` -``` -// detector2.sc -// detecting events in a more appropriate way - player based and event based app -__on_player_starts_sneaking(p) -> -( - global_sneaking = tick_time(); - schedule(40, 'check', p); -); - -check(p) -> -if( global_sneaking && tick_time() - global_sneaking == 40, - sound('entity.shulker.shoot', pos(p), 1, 0.6, 'player'); - signal_event('player_farts', null, p) // trigerring all global apps from a player scoped app -); - -__on_player_stops_sneaking(p) -> (global_sneaking = false); -``` - -And then two other apps that meant to do something with this event. One mimics an explosion, and the other - grows crops around -the player. - -``` -client1.sc -__config() -> {'scope' -> 'global'}; - -on_fart(p, msg) -> -( - print(p+msg); - particle('explosion', pos(p)); - sound('entity.generic.explode', pos(p), 1, 0.5); -); - -handle_event('player_farts', 'on_fart', ' goes kaboom'); -``` - -``` -client2.sc -longsneak(p) -> -( - ppos = pos(p); - loop ( 500, - target = ppos+[rand(12),rand(8),rand(12)]-[rand(12),rand(8),rand(12)]; - if (material(target) == 'plant' && ticks_randomly(target), - particle('happy_villager', target, 2, 0.4) - ); - random_tick(target) - ) -); - -handle_event('player_farts', 'longsneak'); -``` - - ## `/script event` command -used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to +used to display current events and bounded functions. use `add_to` to register a new event, or `remove_from` to unbind a specific function from an event. Function to be bounded to an event needs to have the same number of parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load` -that have functions listed above will be automatically bounded and unbounded when script is unloaded. +that handle specific built-in events will be automatically bounded, and unbounded when script is unloaded. diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index f81da3a5..7c08b451 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -33,10 +33,10 @@ the app state and all players interactions run in the same context, sharing defi explicitly keyed with players, player names, uuids, etc. Even for `'player'` scoped apps, you can access specific player app with with commandblocks using `/execute as run script in run ...`. -To access global/server state for a player app, you need to disown the command from any player, +To access global/server state for a player app, which you shouldn't do, you need to disown the command from any player, so either use a command block, or any arbitrary entity: `/execute as @e[type=bat,limit=1] run script in globals` for instance, however -running anything in the global scope for a `'player'` scoped app is not recommended. +running anything in the global scope for a `'player'` scoped app is not intended. * `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. This is to allow to store multiple apps on the server/world and selectively decide which one should be running at diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index 4b94b3fa..d069fe81 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -5,7 +5,19 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -import carpet.commands.*; +import carpet.commands.CounterCommand; +import carpet.commands.DistanceCommand; +import carpet.commands.DrawCommand; +import carpet.commands.InfoCommand; +import carpet.commands.LogCommand; +import carpet.commands.MobAICommand; +import carpet.commands.PerimeterInfoCommand; +import carpet.commands.PlayerCommand; +import carpet.commands.ProfileCommand; +import carpet.commands.ScriptCommand; +import carpet.commands.SpawnCommand; +import carpet.commands.TestCommand; +import carpet.commands.TickCommand; import carpet.network.ServerNetworkHandler; import carpet.helpers.HopperCounter; import carpet.helpers.TickSpeed; @@ -143,6 +155,7 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit { ServerNetworkHandler.onPlayerJoin(player); LoggerRegistry.playerConnected(player); + scriptServer.onPlayerJoin(player); extensions.forEach(e -> e.onPlayerLoggedIn(player)); } diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java index 29973381..75030545 100644 --- a/src/main/java/carpet/commands/ScriptCommand.java +++ b/src/main/java/carpet/commands/ScriptCommand.java @@ -18,6 +18,7 @@ import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.block.Block; @@ -84,7 +85,7 @@ public class ScriptCommand private static CompletableFuture suggestCode( CommandContext context, SuggestionsBuilder suggestionsBuilder - ) + ) throws CommandSyntaxException { CarpetScriptHost currentHost = getHost(context); String previous = suggestionsBuilder.getRemaining().toLowerCase(Locale.ROOT); @@ -291,7 +292,7 @@ public class ScriptCommand )?1:0)))))). then(literal("remove_from"). then(argument("event", StringArgumentType.word()). - suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().map(ev -> ev.name).collect(Collectors.toList()) ,bb)). + suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.publicEvents(CarpetServer.scriptServer).stream().filter(CarpetEventServer.Event::isNeeded).map(ev -> ev.name).collect(Collectors.toList()) ,bb)). then(argument("call", StringArgumentType.greedyString()). suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.getEvent(StringArgumentType.getString(cc, "event"), CarpetServer.scriptServer).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)). executes( (cc) -> CarpetServer.scriptServer.events.removeEventFromCommand( @@ -311,14 +312,14 @@ public class ScriptCommand suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.modules.keySet(), bb)). then(b).then(u).then(o).then(l).then(s).then(c).then(h).then(i).then(e).then(t)))); } - private static CarpetScriptHost getHost(CommandContext context) + private static CarpetScriptHost getHost(CommandContext context) throws CommandSyntaxException { CarpetScriptHost host; try { String name = StringArgumentType.getString(context, "app").toLowerCase(Locale.ROOT); CarpetScriptHost parentHost = CarpetServer.scriptServer.modules.getOrDefault(name, CarpetServer.scriptServer.globalHost); - host = parentHost.retrieveForExecution(context.getSource()); + host = parentHost.retrieveOwnForExecution(context.getSource()); } catch (IllegalArgumentException ignored) { @@ -327,7 +328,7 @@ public class ScriptCommand host.setChatErrorSnooper(context.getSource()); return host; } - private static Collection suggestFunctionCalls(CommandContext c) + private static Collection suggestFunctionCalls(CommandContext c) throws CommandSyntaxException { CarpetScriptHost host = getHost(c); return host.globaFunctionNames(host.main, s -> !s.startsWith("_")).sorted().collect(Collectors.toList()); @@ -350,7 +351,7 @@ public class ScriptCommand } return 1; } - private static int listGlobals(CommandContext context, boolean all) + private static int listGlobals(CommandContext context, boolean all) throws CommandSyntaxException { CarpetScriptHost host = getHost(context); ServerCommandSource source = context.getSource(); @@ -425,7 +426,7 @@ public class ScriptCommand //host.resetErrorSnooper(); // lets say no need to reset the snooper in case something happens on the way } - private static int invoke(CommandContext context, String call, BlockPos pos1, BlockPos pos2, String args) + private static int invoke(CommandContext context, String call, BlockPos pos1, BlockPos pos2, String args) throws CommandSyntaxException { ServerCommandSource source = context.getSource(); CarpetScriptHost host = getHost(context); @@ -453,7 +454,7 @@ public class ScriptCommand } - private static int compute(CommandContext context, String expr) + private static int compute(CommandContext context, String expr) throws CommandSyntaxException { ServerCommandSource source = context.getSource(); CarpetScriptHost host = getHost(context); @@ -463,7 +464,7 @@ public class ScriptCommand }); } - private static int scriptScan(CommandContext context, BlockPos origin, BlockPos a, BlockPos b, String expr) + private static int scriptScan(CommandContext context, BlockPos origin, BlockPos a, BlockPos b, String expr) throws CommandSyntaxException { ServerCommandSource source = context.getSource(); CarpetScriptHost host = getHost(context); @@ -512,7 +513,7 @@ public class ScriptCommand private static int scriptFill(CommandContext context, BlockPos origin, BlockPos a, BlockPos b, String expr, - BlockStateArgument block, Predicate replacement, String mode) + BlockStateArgument block, Predicate replacement, String mode) throws CommandSyntaxException { ServerCommandSource source = context.getSource(); CarpetScriptHost host = getHost(context); diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index f734c070..04599e8d 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -14,6 +14,7 @@ import carpet.script.value.Value; import carpet.script.value.ValueConversions; import carpet.settings.ParsedRule; import carpet.utils.Messenger; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.BlockState; import net.minecraft.entity.EntityType; import net.minecraft.entity.damage.DamageSource; @@ -47,8 +48,9 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import java.util.function.Function; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Matcher; @@ -65,59 +67,84 @@ public class CarpetEventServer public static class Callback { public final String host; + public final String optionalTarget; public final FunctionValue function; public final List parametrizedArgs; - public Callback(String host, FunctionValue function, List parametrizedArgs) + public Callback(String host, String target, FunctionValue function, List parametrizedArgs) { this.host = host; this.function = function; + this.optionalTarget = target; this.parametrizedArgs = parametrizedArgs==null?NOARGS:parametrizedArgs; } /** * Used also in entity events - * @param asSource - entity command source + * @param sender - entity command source * @param runtimeArgs = options */ - public boolean execute(ServerCommandSource asSource, List runtimeArgs) + public boolean execute(ServerCommandSource sender, List runtimeArgs) { - if (this.parametrizedArgs.isEmpty()) - return CarpetServer.scriptServer.runas( - asSource.withLevel(CarpetSettings.runPermissionLevel), - host, function, runtimeArgs); - else + if (!this.parametrizedArgs.isEmpty()) { - List combinedArgs = new ArrayList<>(runtimeArgs); - combinedArgs.addAll(this.parametrizedArgs); - return CarpetServer.scriptServer.runas( - asSource.withLevel(CarpetSettings.runPermissionLevel), - host, function, combinedArgs); + runtimeArgs = new ArrayList<>(runtimeArgs); + runtimeArgs.addAll(this.parametrizedArgs); } + return CarpetServer.scriptServer.runEventCall( + sender.withLevel(CarpetSettings.runPermissionLevel), + host, optionalTarget, function, runtimeArgs + ); } /** * Used also in entity events - * @param recipient - optional target player argument + * @param sender - sender of the signal + * @param optionalRecipient - optional target player argument * @param runtimeArgs = options */ - public Boolean targetPlayer(ServerPlayerEntity recipient, List runtimeArgs) + public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, List runtimeArgs) { + // recipent of the call doesn't match the handlingHost + if (optionalRecipient != null && !optionalRecipient.getEntityName().equals(optionalTarget)) + return 0; List args = runtimeArgs; if (!this.parametrizedArgs.isEmpty()) { args = new ArrayList<>(runtimeArgs); args.addAll(this.parametrizedArgs); } - return CarpetServer.scriptServer.runForPlayer(recipient, host, function, args); + return CarpetServer.scriptServer.signal(sender, optionalRecipient, host, function, args, false); } @Override public String toString() { - return function.getString()+((host==null)?"":"(from "+host+")"); + return function.getString()+((host==null)?"":"(from "+host+(optionalTarget == null?"":"/"+optionalTarget)+")"); + } + public static class Signature + { + String function; + String host; + String target; + public Signature(String fun, String h, String t) + { + function = fun; + host = h; + target = t; + } + } + public static Signature fromString(String str) + { + Pattern find = Pattern.compile("(\\w+)(?:\\(from (\\w+)(?:/(\\w+))?\\))?"); + Matcher matcher = find.matcher(str); + if(matcher.matches()) + { + return new Signature(matcher.group(1), matcher.group(2), matcher.group(3)); + } + return new Signature(str, null, null); } } @@ -129,7 +156,8 @@ public class CarpetEventServer public ScheduledCall(CarpetContext context, FunctionValue function, List args, long dueTime) { - super(context.host.getName(), function, args); + // ignoring target as we will be always calling self + super(context.host.getName(), null, function, args); this.ctx = context; this.dueTime = dueTime; } @@ -139,7 +167,7 @@ public class CarpetEventServer */ public void execute() { - CarpetServer.scriptServer.runas(ctx.origin, ctx.s, host, function, parametrizedArgs); + CarpetServer.scriptServer.runScheduledCall(ctx.origin, ctx.s, host, (CarpetScriptHost) ctx.host, function, parametrizedArgs); } @@ -173,7 +201,7 @@ public class CarpetEventServer } } - public int callCustom(ServerPlayerEntity receipient, List callArg) + public int signal(ServerCommandSource sender, ServerPlayerEntity optinoalReceipient, List callArg) { if (callList.isEmpty()) return 0; //List fails = new ArrayList<>(); @@ -181,15 +209,13 @@ public class CarpetEventServer int successes = 0; for (Callback call: callList) { - Boolean result = call.targetPlayer(receipient, callArg); - //if (result == Boolean.FALSE) fails.add(call); - if (result == Boolean.TRUE) successes++; + successes += Math.max(0, call.signal(sender, optinoalReceipient, callArg)); } //for (Callback call : fails) callList.remove(call); return successes; } - public boolean addEventCall(ServerCommandSource source, String hostName, String funName, Function verifier) + public boolean addFromExternal(ServerCommandSource source, String hostName, String funName, Consumer hostOnEventHandler) { ScriptHost host = CarpetServer.scriptServer.getHostByName(hostName); if (host == null) @@ -198,11 +224,7 @@ public class CarpetEventServer Messenger.m(source, "r Unknown app "+hostName); return false; } - if (!verifier.apply(host)) - { - Messenger.m(source, "r Global event can only be added to apps with global scope"); - return false; - } + hostOnEventHandler.accept(host); FunctionValue udf = host.getFunction(funName); if (udf == null || udf.getArguments().size() != reqArgs) { @@ -210,11 +232,24 @@ public class CarpetEventServer Messenger.m(source, "r Callback doesn't expect required number of arguments: "+reqArgs); return false; } + String target = null; + if (host.isPerUser()) + { + try + { + target = source.getPlayer().getEntityName(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "r Cannot add event to a player scoped app from a command without a player context"); + return false; + } + } //all clear //remove duplicates - removeEventCall(hostName, udf.getString()); - callList.add(new Callback(hostName, udf, null)); + removeEventCall(hostName, target, udf.getString()); + callList.add(new Callback(hostName, target, udf, null)); return true; } public boolean addEventCallInternal(ScriptHost host, FunctionValue function, List args) @@ -225,20 +260,39 @@ public class CarpetEventServer } //all clear //remove duplicates - removeEventCall(host.getName(), function.getString()); - callList.add(new Callback(host.getName(), function, args)); + removeEventCall(host.getName(), host.user, function.getString()); + callList.add(new Callback(host.getName(), host.user, function, args)); return true; } - public void removeEventCall(String hostName, String funName) + public void removeEventCall(String hostName, String target, String funName) { - callList.removeIf((c)-> c.function.getString().equals(funName) && ( hostName == c.host || (c.host != null && c.host.equalsIgnoreCase(hostName) )) ); + callList.removeIf((c)-> c.function.getString().equals(funName) + && (Objects.equals(c.host, hostName)) + && (Objects.equals(c.optionalTarget, target)) + ); } public void removeAllCalls(CarpetScriptHost host) { - callList.removeIf((c)-> c.host != null && c.host.equals(host.getName())); + callList.removeIf((c)-> (Objects.equals(c.host, host.getName())) + && (Objects.equals(c.optionalTarget, host.user))); } + + public void createChildEvents(CarpetScriptHost host) + { + List copyCalls = new ArrayList<>(); + callList.forEach((c)-> + { + if ((Objects.equals(c.host, host.getName())) + && c.optionalTarget == null) + { + copyCalls.add(new Callback(c.host, host.user, c.function, c.parametrizedArgs)); + } + }); + callList.addAll(copyCalls); + } + public void clearEverything() { callList.clear(); @@ -801,6 +855,12 @@ public class CarpetEventServer host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.removeAllCalls(host)); } + public static void transferAllHostEventsToChild(CarpetScriptHost host) + { + byName.values().forEach((e) -> e.handler.createChildEvents(host)); + host.getScriptServer().events.customEvents.values().forEach((e) -> e.handler.createChildEvents(host)); + } + public static void clearAllBuiltinEvents() { byName.values().forEach(e -> e.handler.clearEverything()); @@ -888,7 +948,7 @@ public class CarpetEventServer { return false; } - boolean added = ev.handler.addEventCall(source, host, funName, h -> canAddEvent(ev, h)); + boolean added = ev.handler.addFromExternal(source, host, funName, h -> onEventAddedToHost(ev, h)); if (added) Messenger.m(source, "gi Added " + funName + " to " + event); return added; } @@ -897,8 +957,7 @@ public class CarpetEventServer { // this is globals only Event ev = Event.byName.get(event); - if (!canAddEvent(ev, host)) - throw new InternalExpressionException("Global event "+event+" can only be added to apps with global scope"); + onEventAddedToHost(ev, host); boolean success = ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); if (!success) throw new InternalExpressionException("Global event "+event+" requires "+ev.handler.reqArgs+", not "+(function.getNumParams()-((args==null)?0:args.size()))); } @@ -906,22 +965,21 @@ public class CarpetEventServer public boolean handleCustomEvent(String event, CarpetScriptHost host, FunctionValue function, List args) { Event ev = Event.getOrCreateCustom(event, host.getScriptServer()); - if (!canAddEvent(ev, host)) - throw new InternalExpressionException("Global built-in event "+event+" can only be handled in apps with global scope"); + onEventAddedToHost(ev, host); return ev.handler.addEventCallInternal(host, function, args==null?NOARGS:args); } - public int signalEvent(String event, CarpetScriptHost host, ServerPlayerEntity player, List callArgs) + public int signalEvent(String event, CarpetContext cc, ServerPlayerEntity optionalTarget, List callArgs) { - Event ev = Event.getEvent(event, host.getScriptServer()); + Event ev = Event.getEvent(event, ((CarpetScriptHost)cc.host).getScriptServer()); if (ev == null) return -1; - return ev.handler.callCustom(player, callArgs); + return ev.handler.signal(cc.s, optionalTarget, callArgs); } - private boolean canAddEvent(Event event, ScriptHost host) + private void onEventAddedToHost(Event event, ScriptHost host) { if (event.deprecated()) host.issueDeprecation(event.name+" event"); - return !(event.globalOnly && (host.perUser || host.parent != null)); + //return !(event.globalOnly && (host.perUser || host.parent != null)); } public boolean removeEventFromCommand(ServerCommandSource source, String event, String funName) @@ -932,8 +990,8 @@ public class CarpetEventServer Messenger.m(source, "r Unknown event: " + event); return false; } - Pair call = decodeCallback(funName); - ev.handler.removeEventCall(call.getLeft(), call.getRight()); + Callback.Signature call = Callback.fromString(funName); + ev.handler.removeEventCall(call.host, call.target, call.function); // could verified if actually removed Messenger.m(source, "gi Removed event: " + funName + " from "+event); return true; @@ -949,13 +1007,15 @@ public class CarpetEventServer public void removeBuiltInEvent(String event, CarpetScriptHost host, String funName) { Event ev = Event.getEvent(event, host.getScriptServer()); - if (ev != null) ev.handler.removeEventCall(host.getName(), funName); + if (ev != null) ev.handler.removeEventCall(host.getName(), host.user, funName); } public void removeAllHostEvents(CarpetScriptHost host) { // remove event handlers Event.removeAllHostEvents(host); + if (host.isPerUser()) + for (ScriptHost child: host.userHosts.values()) Event.removeAllHostEvents((CarpetScriptHost) child); // remove scheduled calls scheduledCalls.removeIf(sc -> sc.host != null && sc.host.equals(host.getName())); } diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 5981a863..f4cf8844 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -1,6 +1,7 @@ package carpet.script; import carpet.CarpetServer; +import carpet.CarpetSettings; import carpet.script.api.Auxiliary; import carpet.script.argument.FunctionArgument; import carpet.script.bundled.Module; @@ -15,7 +16,6 @@ import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; -import carpet.CarpetSettings; import carpet.utils.Messenger; import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -117,7 +117,7 @@ public class CarpetScriptHost extends ScriptHost private static int execute(CommandContext ctx, String hostName, FunctionArgument funcSpec, List paramNames) throws CommandSyntaxException { - CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveForExecution(ctx.getSource()); + CarpetScriptHost cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(ctx.getSource()); List argNames = funcSpec.function.getArguments(); if ((argNames.size()-funcSpec.args.size()) != paramNames.size()) throw new SimpleCommandExceptionType(new LiteralText("Target function "+funcSpec.function.getPrettyString()+" as wrong number of arguments, required "+paramNames.size()+", found "+argNames.size()+" with "+funcSpec.args.size()+" provided")).create(); @@ -177,6 +177,14 @@ public class CarpetScriptHost extends ScriptHost return new CarpetScriptHost(scriptServer, main, false, this, appConfig, appArgTypes, commandValidator, isRuleApp); } + @Override + protected void transferToChild(ScriptHost host) + { + super.transferToChild(host); + // transfer Events + CarpetEventServer.Event.transferAllHostEventsToChild((CarpetScriptHost) host); + } + @Override public void addUserDefinedFunction(Context ctx, Module module, String funName, FunctionValue function) { @@ -319,38 +327,58 @@ public class CarpetScriptHost extends ScriptHost } } - public CarpetScriptHost retrieveForExecution(ServerCommandSource source) + public List retrieveForExecution(ServerCommandSource source, String optionalTarget) { - CarpetScriptHost host = this; + List targets = new ArrayList<>(); if (perUser) { - try + if (optionalTarget == null) { - ServerPlayerEntity player = source.getPlayer(); - host = (CarpetScriptHost) retrieveForExecution(player.getName().getString()); + for (ServerPlayerEntity player : source.getMinecraftServer().getPlayerManager().getPlayerList()) + { + CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName()); + targets.add(host); + if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource()); + } } - catch (CommandSyntaxException e) + else { - host = (CarpetScriptHost) retrieveForExecution((String) null); + ServerPlayerEntity player = source.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget); + if (player != null) + { + CarpetScriptHost host = (CarpetScriptHost) retrieveForExecution(player.getEntityName()); + targets.add(host); + if (host.errorSnooper == null) host.setChatErrorSnooper(player.getCommandSource()); + } } } - if (host.errorSnooper == null) host.setChatErrorSnooper(source); - return host; + else + { + targets.add(this); + if (this.errorSnooper == null) this.setChatErrorSnooper(source); + } + return targets; } - public CarpetScriptHost retrieveForExecution(ServerPlayerEntity target) + public CarpetScriptHost retrieveOwnForExecution(ServerCommandSource source) throws CommandSyntaxException { - // allowing only run - if (perUser == (target == null)) return null; - if (!perUser) { - if (errorSnooper == null) setChatErrorSnooper(this.scriptServer.server.getCommandSource()); + if (errorSnooper == null) setChatErrorSnooper(source); return this; } // user based - CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(target.getName().getString()); - if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(target.getCommandSource()); + ServerPlayerEntity player; + try + { + player = source.getPlayer(); + } + catch (CommandSyntaxException ignored) + { + throw new SimpleCommandExceptionType(new LiteralText("Cannot run player based apps without the player context")).create(); + } + CarpetScriptHost userHost = (CarpetScriptHost)retrieveForExecution(player.getEntityName()); + if (userHost.errorSnooper == null) userHost.setChatErrorSnooper(source); return userHost; } diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index e450460b..17cf32f8 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -1,8 +1,8 @@ package carpet.script; +import carpet.CarpetSettings; import carpet.script.argument.FunctionArgument; import carpet.script.bundled.BundledModule; -import carpet.CarpetSettings; import carpet.CarpetServer; import carpet.script.bundled.FileModule; import carpet.script.bundled.Module; @@ -296,6 +296,11 @@ public class CarpetScriptServer Messenger.m(source, "r Failed to build command system for "+name+" thus failed to load the app"); return false; } + if (newHost.isPerUser()) + { + // that will provide player hosts right at the startup + newHost.retrieveForExecution(source, null); + } long end = System.nanoTime(); CarpetSettings.LOG.info("App "+name+" loaded in "+(end-start)/1000000+" ms"); return true; @@ -325,8 +330,8 @@ public class CarpetScriptServer requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)). executes( (c) -> { - Value response = modules.get(hostName).retrieveForExecution(c.getSource()). - handleCommandLegacy(c.getSource(),"__command", null, ""); + CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),"__command", null, ""); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); return (int)response.readInteger(); }); @@ -355,15 +360,15 @@ public class CarpetScriptServer then(literal(function). requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getFunction(function) != null). executes((c) -> { - Value response = modules.get(hostName).retrieveForExecution(c.getSource()). - handleCommandLegacy(c.getSource(), function, null, ""); + CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, ""); if (!response.isNull()) Messenger.m(c.getSource(), "gi " + response.getString()); return (int) response.readInteger(); }). then(argument("args...", StringArgumentType.greedyString()). executes( (c) -> { - Value response = modules.get(hostName).retrieveForExecution(c.getSource()). - handleCommandLegacy(c.getSource(), function,null, StringArgumentType.getString(c, "args...")); + CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); + Value response = targetHost.handleCommandLegacy(c.getSource(),function, null, StringArgumentType.getString(c, "args...")); if (!response.isNull()) Messenger.m(c.getSource(), "gi "+response.getString()); return (int)response.readInteger(); }))); @@ -394,54 +399,84 @@ public class CarpetScriptServer return true; } - public boolean runas(ServerCommandSource source, String hostname, FunctionValue udf, List argv) - { - return runas(BlockPos.ORIGIN, source, hostname, udf, argv); - } - - public boolean runas(BlockPos origin, ServerCommandSource source, String hostname, FunctionValue udf, List argv) + public boolean runEventCall(ServerCommandSource sender, String hostname, String optionalTarget, FunctionValue udf, List argv) { CarpetScriptHost host = globalHost; - try + if (hostname != null) + host = modules.get(hostname); + if (host == null) return false; + // dummy call for player apps that reside on the global copy - do not run them, but report as successes. + if (host.isPerUser() && optionalTarget==null) return true; + ServerPlayerEntity target = null; + if (optionalTarget != null) { - if (hostname != null) - host = modules.get(hostname).retrieveForExecution(source); - host.callUDF(origin, source, udf, argv); + target = sender.getMinecraftServer().getPlayerManager().getPlayer(optionalTarget); + if (target == null) return false; } - catch (NullPointerException | InvalidCallbackException npe) - { - return false; - } - return true; + int successes = signal(sender, target, hostname, udf, argv, true ); + return successes >= 0; } - public Boolean runForPlayer(ServerPlayerEntity recipient, String hostname, FunctionValue udf, List argv) + public void runScheduledCall(BlockPos origin, ServerCommandSource source, String hostname, CarpetScriptHost host, FunctionValue udf, List argv) + { + if (hostname != null && !modules.containsKey(hostname)) // well - scheduled call app got unloaded + return; + try + { + host.callUDF(origin, source, udf, argv); + } + catch (NullPointerException | InvalidCallbackException ignored) + { + } + } + + /** + * returns number of successful calls in the host or -1 if failed exceptionally + * @param sender + * @param optionalRecipient + * @param hostname + * @param udf + * @param argv + * @param reportFails - returns -1 when any call failed + * @return + */ + public int signal(ServerCommandSource sender, ServerPlayerEntity optionalRecipient, String hostname, FunctionValue udf, List argv, boolean reportFails) { - CarpetScriptHost host; - ServerCommandSource source; if (hostname == null) { - if (recipient != null) return null; - host = globalHost; - source = server.getCommandSource(); + if (optionalRecipient != null) return 0; + try + { + globalHost.callUDF(BlockPos.ORIGIN, sender, udf, argv); + } + catch (NullPointerException | InvalidCallbackException npe) + { + return reportFails?-1:0; + } + return 1; } - else + String hostRecipient = optionalRecipient == null?null:optionalRecipient.getEntityName(); + ServerCommandSource source = (optionalRecipient == null)? sender : optionalRecipient.getCommandSource().withLevel(CarpetSettings.runPermissionLevel); + int successes = 0; + for (CarpetScriptHost host : modules.get(hostname).retrieveForExecution(sender, hostRecipient)) { - host = modules.get(hostname).retrieveForExecution(recipient); - if (host == null) // not applicable - return null; - source = (recipient == null)? server.getCommandSource() : recipient.getCommandSource(); + // getPlayer will always return nonnull cause retrieve for execution only returns hosts for existing players. + ServerCommandSource executingSource = host.perUser + ? source.getMinecraftServer().getPlayerManager().getPlayer(host.user).getCommandSource() + : source.getMinecraftServer().getCommandSource(); + try + { + host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv); + } + catch (NullPointerException | InvalidCallbackException npe) + { + if (reportFails) return -1; + continue; + } + successes ++; } - try - { - host.callUDF(BlockPos.ORIGIN, source.withLevel(CarpetSettings.runPermissionLevel), udf, argv); - } - catch (NullPointerException | InvalidCallbackException npe) - { - return false; - } - return true; + return successes; } public void tick() @@ -461,6 +496,23 @@ public class CarpetScriptServer } } + public void onPlayerJoin(ServerPlayerEntity player) + { + modules.values().forEach(h -> + { + if (h.isPerUser()) + { + try + { + h.retrieveOwnForExecution(player.getCommandSource()); + } + catch (CommandSyntaxException ignored) + { + } + } + }); + } + static class TransferData { boolean perUser; diff --git a/src/main/java/carpet/script/EntityEventsGroup.java b/src/main/java/carpet/script/EntityEventsGroup.java index 76d09f33..3fd3f0b0 100644 --- a/src/main/java/carpet/script/EntityEventsGroup.java +++ b/src/main/java/carpet/script/EntityEventsGroup.java @@ -9,6 +9,7 @@ import carpet.script.value.StringValue; import carpet.script.value.Value; import net.minecraft.entity.Entity; import net.minecraft.entity.damage.DamageSource; +import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.Collections; @@ -19,7 +20,7 @@ import java.util.Map; public class EntityEventsGroup { - private final Map> actions; + private final Map, CarpetEventServer.Callback>> actions; private final Entity entity; public EntityEventsGroup(Entity e) { @@ -30,37 +31,47 @@ public class EntityEventsGroup public void onEvent(Event type, Object... args) { if (actions.isEmpty()) return; // most of the cases, trying to be nice - Map actionSet = actions.get(type); + Map, CarpetEventServer.Callback> actionSet = actions.get(type); if (actionSet == null) return; - for (Iterator> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); ) + for (Iterator, CarpetEventServer.Callback>> iterator = actionSet.entrySet().iterator(); iterator.hasNext(); ) { - Map.Entry action = iterator.next(); - ScriptHost host = CarpetServer.scriptServer.getHostByName(action.getKey()); + Map.Entry, CarpetEventServer.Callback> action = iterator.next(); + Pair key = action.getKey(); + ScriptHost host = CarpetServer.scriptServer.getHostByName(key.getLeft()); if (host == null) { iterator.remove(); continue; } + if (key.getRight() != null) + { + if (entity.getServer().getPlayerManager().getPlayer(key.getRight())==null) + { + iterator.remove(); + continue; + } + } if (!type.call(action.getValue(), entity, args)) iterator.remove(); } if (actionSet.isEmpty()) actions.remove(type); } - public void addEvent(Event type, String hostName, FunctionValue fun, List extraargs) + public void addEvent(Event type, ScriptHost host, FunctionValue fun, List extraargs) { + Pair key = Pair.of(host.getName(), host.user); if (fun != null) { - CarpetEventServer.Callback call = type.create(hostName, fun, extraargs); + CarpetEventServer.Callback call = type.create(key, fun, extraargs); if (call == null) throw new InternalExpressionException("wrong number of arguments for callback, required "+type.argcount); - actions.computeIfAbsent(type, k -> new HashMap<>()).put(hostName, call); + actions.computeIfAbsent(type, k -> new HashMap<>()).put(key, call); } else { - actions.computeIfAbsent(type, k -> new HashMap<>()).remove(hostName); + actions.computeIfAbsent(type, k -> new HashMap<>()).remove(key); if (actions.get(type).isEmpty()) actions.remove(type); } @@ -107,13 +118,13 @@ public class EntityEventsGroup argcount = args+1; // entity is not extra byName.put(identifier, this); } - public CarpetEventServer.Callback create(String hostName, FunctionValue function, List extraArgs) + public CarpetEventServer.Callback create(Pair key, FunctionValue function, List extraArgs) { if ((function.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argcount) { return null; } - return new CarpetEventServer.Callback(hostName, function, extraArgs); + return new CarpetEventServer.Callback(key.getLeft(), key.getRight(), function, extraArgs); } public boolean call(CarpetEventServer.Callback tickCall, Entity entity, Object ... args) { diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index c55861a6..b494c9de 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -318,15 +318,21 @@ public abstract class ScriptHost ScriptHost oldUserHost = userHosts.get(user); if (oldUserHost != null) return oldUserHost; ScriptHost userHost = this.duplicate(); - userHost.modules.putAll(this.modules); - this.moduleData.forEach((key, value) -> userHost.moduleData.put(key, new ModuleData(key, value))); - // fixing imports - userHost.moduleData.forEach((module, data) -> data.setImportsBasedOn(userHost, this.moduleData.get(data.parent))); userHost.user = user; + this.transferToChild(userHost); userHosts.put(user, userHost); return userHost; } + protected void transferToChild(ScriptHost host) + { + // adding imports + host.modules.putAll(this.modules); + this.moduleData.forEach((key, value) -> host.moduleData.put(key, new ModuleData(key, value))); + // fixing imports + host.moduleData.forEach((module, data) -> data.setImportsBasedOn(host, this.moduleData.get(data.parent))); + } + public void handleExpressionException(String msg, ExpressionException exc) { System.out.println(msg+": "+exc); diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index a835fc5c..2105605c 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -961,6 +961,7 @@ public class Auxiliary { { if (lv.size() == 0) throw new InternalExpressionException("'signal' requires at least one argument"); + CarpetContext cc = (CarpetContext)c; CarpetScriptServer server = ((CarpetScriptHost)c.host).getScriptServer(); String eventName = lv.get(0).evalValue(c).getString(); // no such event yet @@ -975,10 +976,10 @@ public class Auxiliary { args = FunctionValue.resolveArgs(lv.subList(2, lv.size()), c, t); } } - int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, (CarpetScriptHost) c.host, player, args); + int counts = ((CarpetScriptHost)c.host).getScriptServer().events.signalEvent(eventName, cc, player, args); if (counts < 0) return LazyValue.NULL; Value ret = new NumericValue(counts); - return (cc, tt) -> ret; + return (c_, t_) -> ret; }); } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 22078193..e1e6d897 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -1372,7 +1372,7 @@ public class EntityValue extends Value EntityEventsGroup.Event event = EntityEventsGroup.Event.byName.get(eventName); if (event == null) throw new InternalExpressionException("Unknown entity event: " + eventName); - ((EntityInterface)entity).getEventContainer().addEvent(event, cc.host.getName(), fun, args); + ((EntityInterface)entity).getEventContainer().addEvent(event, cc.host, fun, args); } @Override diff --git a/src/main/resources/assets/carpet/scripts/distance_beta.sc b/src/main/resources/assets/carpet/scripts/distance_beta.sc index 1865885a..34acbd4f 100644 --- a/src/main/resources/assets/carpet/scripts/distance_beta.sc +++ b/src/main/resources/assets/carpet/scripts/distance_beta.sc @@ -90,12 +90,9 @@ calculate(end)->( ) ); -_carpets() -> system_info('world_carpet_rules'):'carpets'=='true'; - - on_player_places_block(player, item_tuple, hand, block) -> ( - if(block==global_assist_block && _carpets(), + if(block==global_assist_block, 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) @@ -107,7 +104,7 @@ on_player_places_block(player, item_tuple, hand, block) -> on_player_uses_item(player, item_tuple, hand) -> ( - if (item_tuple:0 == global_assist_block && hand == 'mainhand' && _carpets(), + if (item_tuple:0 == global_assist_block && hand == 'mainhand', if (player ~'sneaking', global_current_mode = global_display_modes:(global_display_modes ~ global_current_mode + 1); display_title(player, 'actionbar', format('w Distance mode: ','e '+global_current_mode)); From bba2a1338beaf711997698032da38c5f2cfc4f0b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Dec 2020 04:51:01 +0000 Subject: [PATCH 12/64] Merge docs for 'Merge pull request #601 from gnembon/rework_events rework of the event system, fixes #596' --- docs/scarpet/Full.md | 149 ++++++++++++++----------------------------- 1 file changed, 48 insertions(+), 101 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 6471c35a..c2ae997a 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1733,10 +1733,10 @@ the app state and all players interactions run in the same context, sharing defi explicitly keyed with players, player names, uuids, etc. Even for `'player'` scoped apps, you can access specific player app with with commandblocks using `/execute as run script in run ...`. -To access global/server state for a player app, you need to disown the command from any player, +To access global/server state for a player app, which you shouldn't do, you need to disown the command from any player, so either use a command block, or any arbitrary entity: `/execute as @e[type=bat,limit=1] run script in globals` for instance, however -running anything in the global scope for a `'player'` scoped app is not recommended. +running anything in the global scope for a `'player'` scoped app is not intended. * `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. This is to allow to store multiple apps on the server/world and selectively decide which one should be running at @@ -3816,10 +3816,8 @@ with `__on_` and has the required arguments, will be bound automatically of such function would result in unbinding the app from this event. In case of `player` scoped apps, -all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, cannot be handled -in `'player'` scoped app. - -`'global'` scoped apps will receive both global and player built-in events. +all player action events will be directed to the appropriate player hosts. Global events, like `'tick'`, that don't have a specific +player target will be executed multiple times, once for each player, and once in `'global'` scoped apps. Most built-in events strive to report right before they take an effect in the game. The purpose of that is that this give a choice for the programmer to handle them right away (as it happens, potentially affect the course of action by changing the @@ -3838,8 +3836,8 @@ if it accepts required number of parameters. ## Built-in global events -Handling global events is only allowed in apps with `'global'` scope. With the default scope (`'player'`) you -simply wouldn't know which player to hook up this event to. +Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance + is responsible independently from handling their events, so a global event may be executed multiple times for each player. ### `__on_tick()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` @@ -4012,8 +4010,11 @@ is handled before scoreboard values for these statistics are changed. App programmers can define and trigger their own custom events. Unlike built-in events, all custom events pass a single value as an argument, but this doesn't mean that they cannot pass a complex list, map, or nbt tag as a message. Each event signal is -either targetting all global context apps, if no target player has been identified, or only player scoped apps, if the target player -is specified. +either targetting all apps instances for all players, including global apps, if no target player has been identified, +or only player scoped apps, if the target player +is specified, running once for that player app. You cannot target global apps with player-targeted signals. Built-in events +do target global apps, since their first argument is clearly defined and passed. That may change in the future in case there is +a compelling argument to be able to target global apps with player scopes. Programmers can also handle built-in events the same way as custom events, as well as triggering built-in events, which I have have no idea why you would need that. The following snippets have the same effect: @@ -4037,11 +4038,11 @@ handle_event('player_breaks_block', null); And `signal_event` can be used as a trigger, called twice for player based built-in events ``` signal_event('player_breaks_block', player, player, block); // to target all player scoped apps -signal_event('player_breaks_block', null , player, block); // to target all global scoped apps +signal_event('player_breaks_block', null , player, block); // to target all global scoped apps and all player instances ``` -or (for global only events) +or (for global events) ``` -signal_event('tick') // trigger only global scoped apps with no extra arguments +signal_event('tick') // trigger all apps with a tick event ``` ### `handle_event(event, callback ...)` @@ -4055,6 +4056,8 @@ If extra arguments are provided, they will be appended to the argument list of t Returns `true` if subscription to the event was successful, or `false` if it failed (for instance wrong scope for built-in event, or incorect number of parameters for the event). +If a callback is specified as `null`, the given app (or player app instance )stops handling that event. +
 foo(a) -> print(a);
 handle_event('boohoo', 'foo');
@@ -4069,114 +4072,58 @@ handle_event('tick', null)  // nah, ima good, kthxbai
 
 ### `signal_event(event, target_player?, ... args?)`
 
-Fires a specific event. If the event does not exist (only `handle_event` creates missing mising new events), or provided argument list
+Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list
 was not matching the callee expected arguments, returns `null`, 
 otherwise returns number of apps notified. If `target_player` is specified and not `null` triggers a player specific event, targetting
 only `player` scoped apps for that player. Apps with globals scope will not be notified even if they handle this event.
-If the `target_player` is omitted or `null`, it will instead only target `global` scoped apps and skip `player` scoped.
-Therefore each custom event trigger can trigger each app once, but calls to '`signal_event`' can be repeated to cover
-all app scopes. Note that all built-in player events have a player as a first argument, so to trigger these events, you need to 
+If the `target_player` is omitted or `null`, it will target `global` scoped apps and all instances of `player` scoped apps.
+Note that all built-in player events have a player as a first argument, so to trigger these events, you need to 
 provide them twice - once to specify the target player scope and second - to provide as an argument to the handler function.
 
 
 signal_event('player_breaks_block', player, player, block); // to target all player scoped apps
-signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps
-signal_event('tick') // triggering tick event in all loaded apps
+signal_event('player_breaks_block', null  , player, block); // to target all global scoped apps and all player instances
+signal_event('tick') // trigger all apps with a tick event
 
## Custom events example -The following example contains 4 apps, 2 of them defining a new event, `"player_farts"` triggered after 40 ticks of continuous -squatting. Note that this could call the recipient event handlers twice, since `signal_event` will be called twice, but -in this case the event is once called as a global event, and once as a player event, targeting each app once. Custom events is -a good idea to share common event detection code with one app, and using the outcome of these events in multiple places. -Obviously you would define each event handler once not twice in your app system. - -In this -case here we have two ways of detecting all players squatting for more than 40 ticks - one of those apps will produce cloud particles -in an apprpriate location, and the other will produce a sound when it happens, and they both will proceed to signal the same event, -thankfully for different scopes, otherwise other apps would trigger twice. +The following example shows how you can communicate between different instances of the same player scoped app. It important to note +that signals can trigger other apps as well, assuming the name of the event matches. In this case the request name is called +`tp_request` and is triggered with a command. + ``` -// detector1.sc -// detecting event in a tick loop -__config() -> {'scope' -> 'global'}; - -global_sneaks = {}; -__on_tick() -> for(player('all'), - if (_~'sneaking', - sneak_time = if (has(global_sneaks:_), tick_time()-global_sneaks:_ , global_sneaks:_=tick_time(); 0); - if (sneak_time > 20 && sneak_time < 40, - particle('cloud', pos(_) + [0, _~'height'/2, 0] - _~'look', 1, 0.1) - ); - if (sneak_time == 40, - signal_event('player_farts', _, _) // triggering all player scoped apps from a global app +// tpa.sc +global_requester = null; +__config() -> { + 'commands' -> { + '' -> _(to) -> signal_event('tp_request', to, player()), + 'accept' -> _() -> if(global_requester, + run('tp '+global_requester~'command_name'); + global_requester = null ) - , // else - delete(global_sneaks:_) - ) -) + }, + 'arguments' -> { + 'player' -> {'type' -> 'players', 'single' -> true} + } +}; +handle_event('tp_request', _(req) -> ( + global_requester = req; + print(player(), format( + 'w '+req+' requested to teleport to you. Click ', + 'yb here', '^yb here', '!/tpa accept', + 'w to accept it.' + )); +)); ``` -``` -// detector2.sc -// detecting events in a more appropriate way - player based and event based app -__on_player_starts_sneaking(p) -> -( - global_sneaking = tick_time(); - schedule(40, 'check', p); -); - -check(p) -> -if( global_sneaking && tick_time() - global_sneaking == 40, - sound('entity.shulker.shoot', pos(p), 1, 0.6, 'player'); - signal_event('player_farts', null, p) // trigerring all global apps from a player scoped app -); - -__on_player_stops_sneaking(p) -> (global_sneaking = false); -``` - -And then two other apps that meant to do something with this event. One mimics an explosion, and the other - grows crops around -the player. - -``` -client1.sc -__config() -> {'scope' -> 'global'}; - -on_fart(p, msg) -> -( - print(p+msg); - particle('explosion', pos(p)); - sound('entity.generic.explode', pos(p), 1, 0.5); -); - -handle_event('player_farts', 'on_fart', ' goes kaboom'); -``` - -``` -client2.sc -longsneak(p) -> -( - ppos = pos(p); - loop ( 500, - target = ppos+[rand(12),rand(8),rand(12)]-[rand(12),rand(8),rand(12)]; - if (material(target) == 'plant' && ticks_randomly(target), - particle('happy_villager', target, 2, 0.4) - ); - random_tick(target) - ) -); - -handle_event('player_farts', 'longsneak'); -``` - - ## `/script event` command -used to display current events and bounded functions. use `add_to` ro register new event, or `remove_from` to +used to display current events and bounded functions. use `add_to` to register a new event, or `remove_from` to unbind a specific function from an event. Function to be bounded to an event needs to have the same number of parameters as the action is attempting to bind to (see list above). All calls in modules loaded via `/script load` -that have functions listed above will be automatically bounded and unbounded when script is unloaded. +that handle specific built-in events will be automatically bounded, and unbounded when script is unloaded. # Scoreboard ### `scoreboard()`, `scoreboard(objective)`, `scoreboard(objective, key)`, `scoreboard(objective, key, value)` From 632d7c3268dc7f3c790ec5643dfb7c29c792239f Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 2 Dec 2020 18:15:26 -0400 Subject: [PATCH 13/64] 1.4.20 --- gradle.properties | 2 +- src/main/java/carpet/CarpetSettings.java | 2 +- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8caaf1a1..6080a184 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.10.8 # Mod Properties - mod_version = 1.4.19+v201125 + mod_version = 1.4.20+v201202 maven_group = carpet archives_base_name = fabric-carpet diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 4a72fe1b..fbe1ac98 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -36,7 +36,7 @@ import static carpet.settings.RuleCategory.CLIENT; @SuppressWarnings("CanBeFinal") public class CarpetSettings { - public static final String carpetVersion = "1.4.19+v201125"; + public static final String carpetVersion = "1.4.20+v201202"; public static final Logger LOG = LogManager.getLogger("carpet"); public static boolean skipGenerationChecks = false; public static boolean impendingFillSkipUpdates = false; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 182e7e0e..c8144ace 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "carpet", - "version": "1.4.19", + "version": "1.4.20", "name": "Carpet Mod in Fabric", "description": "Carpet made out of fabric", From 259bdc2c94a298a2a0705a6e4af406aab53acf01 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 2 Dec 2020 22:36:20 -0400 Subject: [PATCH 14/64] ability to query client brand and server scarpet version. --- docs/scarpet/api/Auxiliary.md | 6 ++++-- docs/scarpet/api/Entities.md | 5 +++++ src/main/java/carpet/network/ServerNetworkHandler.java | 7 +++++++ src/main/java/carpet/script/utils/SystemInfo.java | 10 ++-------- src/main/java/carpet/script/value/EntityValue.java | 9 +++++++++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index e5e9209b..f759b4fd 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -500,7 +500,6 @@ Available options in the scarpet app space: * `game_version` - base version of the game Server related properties - * `server_motd` - the motd of the server visible when joining * `server_ip` - IP adress of the game hosted * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players @@ -516,4 +515,7 @@ Available options in the scarpet app space: * `java_version` - version of Java * `java_bits` - number indicating how many bits the Java has, 32 or 64 * `java_system_cpu_load` - current percentage of CPU used by the system - * `java_process_cpu_load` - current percentage of CPU used by JVM \ No newline at end of file + * `java_process_cpu_load` - current percentage of CPU used by JVM + + Scarpet related properties + * `scarpet_version` - returns the version of the carpet your scarpet comes with. \ No newline at end of file diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index a60d426c..e73c0697 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -366,6 +366,11 @@ Player's ping in milliseconds, or `null` if its not a player. Player's permission level, or `null` if not applicable for this entity. +### `query(e, 'client_brand')` + +Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet '` where +version indicates the version of the connected carpet client. + ### `query(e, 'effect', name?)` Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short diff --git a/src/main/java/carpet/network/ServerNetworkHandler.java b/src/main/java/carpet/network/ServerNetworkHandler.java index 5971e93d..72ffc14f 100644 --- a/src/main/java/carpet/network/ServerNetworkHandler.java +++ b/src/main/java/carpet/network/ServerNetworkHandler.java @@ -217,6 +217,13 @@ public class ServerNetworkHandler } + public static String getPlayerStatus(ServerPlayerEntity player) + { + if (remoteCarpetPlayers.containsKey(player)) return "carpet "+remoteCarpetPlayers.get(player); + if (validCarpetPlayers.contains(player)) return "carpet "+CarpetSettings.carpetVersion; + return "vanilla"; + } + private static class DataBuilder { private CompoundTag tag; diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 2dc048db..86b13735 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -1,6 +1,7 @@ package carpet.script.utils; import carpet.CarpetServer; +import carpet.CarpetSettings; import carpet.script.CarpetContext; import carpet.script.CarpetScriptHost; import carpet.script.value.ListValue; @@ -123,14 +124,7 @@ public class SystemInfo { }); return carpetRules; }); - - - - - - - - + put("scarpet_version", c -> StringValue.of(CarpetSettings.carpetVersion)); }}; public static Value get(String what, CarpetContext cc) diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index e1e6d897..cb6aa894 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -9,6 +9,7 @@ import carpet.fakes.MobEntityInterface; import carpet.fakes.HungerManagerInterface; import carpet.fakes.ServerPlayerInteractionManagerInterface; import carpet.helpers.Tracer; +import carpet.network.ServerNetworkHandler; import carpet.patches.EntityPlayerMPFake; import carpet.script.CarpetContext; import carpet.script.EntityEventsGroup; @@ -579,6 +580,14 @@ public class EntityValue extends Value return Value.NULL; }); + put("client_brand", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + return StringValue.of(ServerNetworkHandler.getPlayerStatus((ServerPlayerEntity) e)); + } + return Value.NULL; + }); + put("team", (e, a) -> e.getScoreboardTeam()==null?Value.NULL:new StringValue(e.getScoreboardTeam().getName())); put("ping", (e, a) -> { From 6a687177ab809270a3b732a19ca206af400ce5cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Dec 2020 02:36:49 +0000 Subject: [PATCH 15/64] Merge docs for 'ability to query client brand and server scarpet version.' --- docs/scarpet/Full.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index c2ae997a..eb470336 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -3170,6 +3170,11 @@ Player's ping in milliseconds, or `null` if its not a player. Player's permission level, or `null` if not applicable for this entity. +### `query(e, 'client_brand')` + +Returns recognized type of client of the client connected. Possible results include `'vanilla'`, or `'carpet '` where +version indicates the version of the connected carpet client. + ### `query(e, 'effect', name?)` Without extra arguments, it returns list of effect active on a living entity. Each entry is a triple of short @@ -4791,7 +4796,6 @@ Available options in the scarpet app space: * `game_version` - base version of the game Server related properties - * `server_motd` - the motd of the server visible when joining * `server_ip` - IP adress of the game hosted * `server_whitelisted` - boolean indicating whether the access to the server is only for whitelisted players @@ -4807,7 +4811,10 @@ Available options in the scarpet app space: * `java_version` - version of Java * `java_bits` - number indicating how many bits the Java has, 32 or 64 * `java_system_cpu_load` - current percentage of CPU used by the system - * `java_process_cpu_load` - current percentage of CPU used by JVM# `/script run` command + * `java_process_cpu_load` - current percentage of CPU used by JVM + + Scarpet related properties + * `scarpet_version` - returns the version of the carpet your scarpet comes with.# `/script run` command Primary way to input commands. The command executes in the context, position, and dimension of the executing player, commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and From 5a15c115ac3fb0b9cd0e57dca7192fc13e11a90e Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 3 Dec 2020 00:44:27 -0400 Subject: [PATCH 16/64] compound tags from scarpet are saved compressed and allowing to read compressed compounds as well. --- .../java/carpet/script/api/Auxiliary.java | 14 ++-- .../carpet/script/bundled/FileModule.java | 69 ++++++++++++++----- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 2105605c..1ec278f8 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -88,12 +88,12 @@ 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 String recognizeResource(Value value) + public static String recognizeResource(Value value, boolean isFloder) { String origfile = value.getString(); String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", ""); file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/")); - if (file.isEmpty()) + if (file.isEmpty() && !isFloder) { throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers"); } @@ -786,7 +786,7 @@ public class Auxiliary { }); expression.addLazyFunction("read_file", 2, (c, t, lv) -> { - String resource = recognizeResource(lv.get(0).evalValue(c)); + String resource = recognizeResource(lv.get(0).evalValue(c), false); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) @@ -809,7 +809,7 @@ public class Auxiliary { }); expression.addLazyFunction("delete_file", 2, (c, t, lv) -> { - String resource = recognizeResource(lv.get(0).evalValue(c)); + String resource = recognizeResource(lv.get(0).evalValue(c), false); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) @@ -821,7 +821,7 @@ public class Auxiliary { expression.addLazyFunction("write_file", -1, (c, t, lv) -> { if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments"); - String resource = recognizeResource(lv.get(0).evalValue(c)); + String resource = recognizeResource(lv.get(0).evalValue(c), false); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); boolean shared = origtype.startsWith("shared_"); String type = shared ? origtype.substring(7) : origtype; //len(shared_) @@ -874,7 +874,7 @@ public class Auxiliary { if (lv.size()>0) { c.host.issueDeprecation("load_app_data(...) with arguments"); - file = recognizeResource(lv.get(0).evalValue(c)); + file = recognizeResource(lv.get(0).evalValue(c), false); if (lv.size() > 1) { shared = lv.get(1).evalValue(c).getBoolean(); @@ -897,7 +897,7 @@ public class Auxiliary { if (lv.size()>1) { c.host.issueDeprecation("store_app_data(...) with more than one argument"); - file = recognizeResource(lv.get(1).evalValue(c)); + file = recognizeResource(lv.get(1).evalValue(c), false); if (lv.size() > 2) { shared = lv.get(2).evalValue(c).getBoolean(); diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index cacc2e12..4528a17f 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -1,5 +1,7 @@ package carpet.script.bundled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.PositionTracker; import net.minecraft.nbt.Tag; import net.minecraft.nbt.TagReaders; @@ -60,25 +62,40 @@ public class FileModule extends Module } //copied private method from net.minecraft.nbt.NbtIo.read() + // to read non-compound tags - these won't be compressed public static Tag read(File file) { - try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file))) + try { - byte byte_1 = dataInput_1.readByte(); - if (byte_1 == 0) + return NbtIo.readCompressed(file); + } + catch (IOException e) + { + try { + return NbtIo.read(file); + } + catch (IOException ioException) + { + try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file))) + { + byte byte_1 = dataInput_1.readByte(); + if (byte_1 == 0) + { + return null; + } + else + { + dataInput_1.readUTF(); + return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT); + } + } + catch (IOException ignored) + { + } return null; } - else - { - dataInput_1.readUTF(); - return TagReaders.of(byte_1).read(dataInput_1, 0, PositionTracker.DEFAULT); - } } - catch (IOException ignored) - { - } - return null; } //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write @@ -87,18 +104,32 @@ public class FileModule extends Module File file_2 = new File(file.getAbsolutePath() + "_tmp"); if (file_2.exists()) file_2.delete(); - try(DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2))) + if (tag_1 instanceof CompoundTag) { - dataOutputStream_1.writeByte(tag_1.getType()); - if (tag_1.getType() != 0) + try { - dataOutputStream_1.writeUTF(""); - tag_1.write(dataOutputStream_1); + NbtIo.writeCompressed((CompoundTag) tag_1, file_2); + } + catch (IOException e) + { + return false; } } - catch (IOException e) + else { - return false; + try (DataOutputStream dataOutputStream_1 = new DataOutputStream(new FileOutputStream(file_2))) + { + dataOutputStream_1.writeByte(tag_1.getType()); + if (tag_1.getType() != 0) + { + dataOutputStream_1.writeUTF(""); + tag_1.write(dataOutputStream_1); + } + } + catch (IOException e) + { + return false; + } } if (file.exists()) file.delete(); if (!file.exists()) file_2.renameTo(file); From 6e5af5bfc899db887f54670ab345bc2d031022bb Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 3 Dec 2020 04:05:11 -0400 Subject: [PATCH 17/64] added list_files, fixes #589 --- docs/scarpet/api/Auxiliary.md | 33 ++++---- docs/scarpet/resources/editors/idea/2.txt | 1 + .../scarpet/resources/editors/npp/scarpet.xml | 2 +- .../java/carpet/script/CarpetScriptHost.java | 7 ++ .../carpet/script/CarpetScriptServer.java | 4 +- .../java/carpet/script/api/Auxiliary.java | 76 ++++++++++++------- .../carpet/script/bundled/FileModule.java | 15 ++++ .../java/carpet/script/bundled/Module.java | 62 +++++++++------ 8 files changed, 126 insertions(+), 74 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index f759b4fd..6362b82d 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -275,13 +275,15 @@ Available output types: ### `read_file(resource, type)` ### `delete_file(resource, type)` ### `write_file(resource, type, data, ...)` +### `list_files(resource, type)` With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its - content, or deletes the resource. + content, deletes the resource, or lists other files under this resource. Resource is identified by a path to the file. A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped -from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred +from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder. + Do not add file extensions to the descriptor - extensions are inferred based on the `type` of the file. Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific @@ -296,33 +298,26 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s The default no-name app, via `/script run` command can only save/load/read files from the shared space. -Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true` -for success writes and deletes, and requested data, based on the file type, for read operations. - -NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data. - -Write operations to text files always result in appending to the existing file, so consecutive writes will increase -the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to -write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the -third argument. - * `write_file('temp', 'text', 'foo', 'bar', 'baz')` or - * write_file('temp', 'text', l('foo', 'bar', 'baz')) +Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true` +for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files +for folder listing. -To log a single line of string - -Supported values for resource `type` is: +Supported values for resource `type` are: * `nbt` - NBT tag * `text` - text resource with automatic newlines added * `raw` - text resource without implied newlines - * `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above + * `folder` - for `list_files` only - indicting folder listing instead of files + * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each -record to the file. +record to the file. Since files are closed after each write, sending multiple lines of data to +write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the +third argument.
-write_file('foo', 'shared_text, l('one', 'two'));
+write_file('foo', 'shared_text, ['one', 'two']);
 write_file('foo', 'shared_text', 'three\n', 'four\n');
 write_file('foo', 'shared_raw', 'five\n', 'six\n');
 
diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt
index 8f826f39..e7f1f8f6 100644
--- a/docs/scarpet/resources/editors/idea/2.txt
+++ b/docs/scarpet/resources/editors/idea/2.txt
@@ -55,6 +55,7 @@ item_category
 last_tick_times
 light
 liquid
+list_files
 load_app_data
 loaded
 loaded_status
diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml
index 5dba63e7..46f3a139 100644
--- a/docs/scarpet/resources/editors/npp/scarpet.xml
+++ b/docs/scarpet/resources/editors/npp/scarpet.xml
@@ -82,7 +82,7 @@
 
                 plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
 
-                load_app_data store_app_data read_file write_file delete_file
+                load_app_data store_app_data read_file write_file delete_file list_files
                 escape_nbt parse_nbt encode_nbt tag_matches statistic
             
             euler pi null true false
diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java
index f4cf8844..4a5a02ef 100644
--- a/src/main/java/carpet/script/CarpetScriptHost.java
+++ b/src/main/java/carpet/script/CarpetScriptHost.java
@@ -41,6 +41,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static java.lang.Math.max;
 import static net.minecraft.server.command.CommandManager.literal;
@@ -673,6 +674,12 @@ public class CarpetScriptHost extends ScriptHost
         return Module.listFile(main, resource, "txt", isShared);
     }
 
+    public Stream listFolder(String resource, String ext, boolean isShared)
+    {
+        if (getName() == null && !isShared) return null; //
+        return Module.listFolder(main, resource, ext, isShared);
+    }
+
 
     public void tick()
     {
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index 17cf32f8..d7090999 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -137,7 +137,7 @@ public class CarpetScriptServer
             Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
             Files.createDirectories(folder);
             Optional
-            scriptPath = Files.walk(folder)
+            scriptPath = Files.list(folder)
                 .filter(script -> 
                     script.getFileName().toString().equalsIgnoreCase(name+".sc") || 
                     (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl"))
@@ -149,7 +149,7 @@ public class CarpetScriptServer
             {
                 Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
                 Files.createDirectories(globalFolder);
-                scriptPath = Files.walk(globalFolder)
+                scriptPath = Files.list(globalFolder)
                         .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
                                 (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
                         .findFirst();
diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java
index 1ec278f8..1c8503ef 100644
--- a/src/main/java/carpet/script/api/Auxiliary.java
+++ b/src/main/java/carpet/script/api/Auxiliary.java
@@ -32,6 +32,7 @@ import carpet.script.value.StringValue;
 import carpet.script.value.Value;
 import carpet.script.value.ValueConversions;
 import carpet.utils.Messenger;
+import com.google.common.collect.ImmutableMap;
 import net.minecraft.block.BlockState;
 import net.minecraft.command.argument.EntityAnchorArgumentType;
 import net.minecraft.entity.Entity;
@@ -50,7 +51,6 @@ import net.minecraft.particle.ParticleEffect;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.command.CommandOutput;
 import net.minecraft.server.command.ServerCommandSource;
-import net.minecraft.server.network.ServerPlayNetworkHandler;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.server.world.ServerWorld;
 import net.minecraft.sound.SoundCategory;
@@ -69,6 +69,7 @@ import net.minecraft.util.registry.DynamicRegistryManager;
 import net.minecraft.util.registry.Registry;
 import net.minecraft.world.World;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -87,6 +88,12 @@ 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));
+    private static final Map supportedTypes = ImmutableMap.of(
+            "raw", ".txt",
+            "text", ".txt",
+            "nbt", ".nbt",
+            "folder", "folder"
+    );
 
     public static String recognizeResource(Value value, boolean isFloder)
     {
@@ -100,6 +107,20 @@ public class Auxiliary {
         return file;
     }
 
+    public static Triple getFileDescriptor(List lv, Context c, boolean isFloder)
+    {
+        if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
+        String resource = recognizeResource(lv.get(0).evalValue(c), isFloder);
+        String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
+        boolean shared = origtype.startsWith("shared_");
+        String type = shared ? origtype.substring(7) : origtype; //len(shared_)
+        if (!supportedTypes.containsKey(type))
+            throw new InternalExpressionException("Unsupported file type: "+origtype);
+        if (type.equals("folder") && !isFloder)
+            throw new InternalExpressionException("Folder types are no supported for this IO function");
+        return Triple.of(resource, type, shared);
+    }
+
     public static void apply(Expression expression)
     {
         expression.addLazyFunction("sound", -1, (c, t, lv) -> {
@@ -785,23 +806,29 @@ public class Auxiliary {
             return (_c, _t) -> res; // pass through for variables
         });
 
-        expression.addLazyFunction("read_file", 2, (c, t, lv) -> {
-            String resource = recognizeResource(lv.get(0).evalValue(c), false);
-            String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
-            boolean shared = origtype.startsWith("shared_");
-            String type = shared ? origtype.substring(7) : origtype; //len(shared_)
-            if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
-                throw new InternalExpressionException("Unsupported file type: "+origtype);
+        expression.addLazyFunction("list_files", 2, (c, t, lv) ->
+        {
+            Triple fdesc = getFileDescriptor(lv, c, true);
+            Stream files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight());
+            if (files == null) return LazyValue.NULL;
+            Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
+            return (cc, tt) -> ret;
+        });
+
+
+        expression.addLazyFunction("read_file", 2, (c, t, lv) ->
+        {
+            Triple fdesc = getFileDescriptor(lv, c, false);
             Value retVal;
-            if (type.equals("nbt"))
+            if (fdesc.getMiddle().equals("nbt"))
             {
-                Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared);
+                Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
                 if (state == null) return LazyValue.NULL;
                 retVal = new NBTSerializableValue(state);
             }
             else
             {
-                List content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared);
+                List content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getRight());
                 if (content == null) return LazyValue.NULL;
                 retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
             }
@@ -809,33 +836,26 @@ public class Auxiliary {
         });
 
         expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
-            String resource = recognizeResource(lv.get(0).evalValue(c), false);
-            String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
-            boolean shared = origtype.startsWith("shared_");
-            String type = shared ? origtype.substring(7) : origtype; //len(shared_)
-            if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
-                throw new InternalExpressionException("Unsupported file type: "+origtype);
-            boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type);
-            return success?LazyValue.TRUE:LazyValue.FALSE;
+            Triple fdesc = getFileDescriptor(lv, c, false);
+            return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
+                    ? LazyValue.TRUE
+                    : LazyValue.FALSE;
         });
 
         expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
             if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
-            String resource = recognizeResource(lv.get(0).evalValue(c), false);
-            String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
-            boolean shared = origtype.startsWith("shared_");
-            String type = shared ? origtype.substring(7) : origtype; //len(shared_)
-            if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
-                throw new InternalExpressionException("Unsupported file type: "+origtype);
+            Triple fdesc = getFileDescriptor(lv, c, false);
+
+
             boolean success;
-            if (type.equals("nbt"))
+            if (fdesc.getMiddle().equals("nbt"))
             {
                 Value val = lv.get(2).evalValue(c);
                 NBTSerializableValue tagValue =  (val instanceof NBTSerializableValue)
                         ? (NBTSerializableValue) val
                         : new NBTSerializableValue(val.getString());
                 Tag tag = tagValue.getTag();
-                success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared);
+                success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
             }
             else
             {
@@ -860,7 +880,7 @@ public class Auxiliary {
                         data.add(lv.get(i).evalValue(c).getString());
                     }
                 }
-                success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data);
+                success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
             }
             return success?LazyValue.TRUE:LazyValue.FALSE;
         });
diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java
index 4528a17f..b710b376 100644
--- a/src/main/java/carpet/script/bundled/FileModule.java
+++ b/src/main/java/carpet/script/bundled/FileModule.java
@@ -23,6 +23,7 @@ import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.stream.Stream;
 
 public class FileModule extends Module
 {
@@ -175,4 +176,18 @@ public class FileModule extends Module
             return null;
         }
     }
+
+    public static Stream listFiles(Path dir, String ext)
+    {
+        boolean folderListing = ext.equals("folder");
+        try
+        {
+            return Files.list(dir).
+                    filter(path -> folderListing?Files.isDirectory(path):path.toString().endsWith(ext));
+        }
+        catch (IOException ignored)
+        {
+            return null;
+        }
+    }
 }
diff --git a/src/main/java/carpet/script/bundled/Module.java b/src/main/java/carpet/script/bundled/Module.java
index 1916e0ba..3bf5f8ce 100644
--- a/src/main/java/carpet/script/bundled/Module.java
+++ b/src/main/java/carpet/script/bundled/Module.java
@@ -3,10 +3,12 @@ package carpet.script.bundled;
 import carpet.CarpetServer;
 import net.minecraft.nbt.Tag;
 import net.minecraft.util.WorldSavePath;
+import org.apache.commons.io.FilenameUtils;
 
-import java.io.File;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.List;
+import java.util.stream.Stream;
 
 public abstract class Module
 {
@@ -19,61 +21,73 @@ public abstract class Module
     {
         if (isShared)
         {
-            return "shared/"+file;
+            return file.isEmpty()?"shared":"shared/"+file;
         }
-        else if (module != null && module.getName() != null) // appdata
+        if (module != null && module.getName() != null) // appdata
         {
-            return module.getName()+".data"+(file==null?"":"/"+file);
-        }
-        else
-        {
-            throw  new RuntimeException("Invalid file descriptor: "+file);
+            return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
         }
+        throw  new RuntimeException("Invalid file descriptor: "+file);
     }
 
     public static Tag getData(Module module, String file, boolean isShared)
     {
-        File dataFile = resolveResource(module, file, "nbt", isShared);
+        Path dataFile = resolveResource(module, file, "nbt", isShared);
         if (dataFile == null) return null;
-        if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null;
-        synchronized (writeIOSync) { return FileModule.read(dataFile); }
+        if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
+        synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
     }
 
     public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
     {
-        File dataFile = resolveResource(module, file, "nbt", isShared);
+        Path dataFile = resolveResource(module, file, "nbt", isShared);
         if (dataFile == null) return false;
-        if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync) { return FileModule.write(globalState, dataFile); }
+        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
+        synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
     }
 
     public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List message)
     {
-        File dataFile = resolveResource(module, resourceName, "txt", isShared);
+        Path dataFile = resolveResource(module, resourceName, "txt", isShared);
         if (dataFile == null) return false;
-        if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false;
-        synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
+        if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
+        synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), type.equals("text"), message); }
     }
 
     public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared)
     {
-        File dataFile = resolveResource(module, resourceName, ext, isShared);
+        Path dataFile = resolveResource(module, resourceName, ext, isShared);
         if (dataFile == null) return false;
-        synchronized (writeIOSync) { return dataFile.delete(); }
+        synchronized (writeIOSync) { return dataFile.toFile().delete(); }
     }
 
     public static List listFile(Module module, String resourceName, String ext, boolean isShared)
     {
-        File dataFile = resolveResource(module, resourceName, ext, isShared);
+        Path dataFile = resolveResource(module, resourceName, ext, isShared);
         if (dataFile == null) return null;
-        if (!dataFile.exists()) return null;
-        synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
+        if (!dataFile.toFile().exists()) return null;
+        synchronized (writeIOSync) { return FileModule.listFileContent(dataFile.toFile()); }
     }
 
-    private static File resolveResource(Module module, String resourceName, String ext, boolean isShared)
+    public static Stream listFolder(Module module, String resourceName, String ext, boolean isShared)
+    {
+        Path dir = resolveResource(module, resourceName, null, isShared);
+        if (dir == null) return null;
+        if (!Files.exists(dir)) return null;
+        Stream result;
+        synchronized (writeIOSync) { result = FileModule.listFiles(dir, ext); }
+        if (result == null) return null;
+        Path rootPath = resolveResource(module, "", null, isShared);
+        Stream strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
+        if (!ext.equals("folder"))
+            return strings.map(FilenameUtils::removeExtension);
+        return strings;
+    }
+
+    private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
     {
         if (!isShared && (module == null || module.getName() == null)) return null;
-        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+"."+ext).toFile();
+        return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":"."+ext));
     }
 
     @Override

From 33bf24fa860ae93b2498b22270bc7ea106a9579b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]" 
Date: Thu, 3 Dec 2020 08:05:37 +0000
Subject: [PATCH 18/64] Merge docs for 'added list_files, fixes #589'

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

diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md
index eb470336..bd8c1ae3 100644
--- a/docs/scarpet/Full.md
+++ b/docs/scarpet/Full.md
@@ -4571,13 +4571,15 @@ Available output types:
 ### `read_file(resource, type)`
 ### `delete_file(resource, type)`
 ### `write_file(resource, type, data, ...)`
+### `list_files(resource, type)`
 
 With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its
- content, or deletes the resource.
+ content, deletes the resource, or lists other files under this resource.
 
 Resource is identified by a path to the file.  
 A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
-from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred
+from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder.
+ Do not add file extensions to the descriptor - extensions are inferred
 based on the `type` of the file.
  
 Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
@@ -4592,33 +4594,26 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s
 
 The default no-name app, via `/script run` command can only save/load/read files from the shared space.
 
-Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true`
-for success writes and deletes, and requested data, based on the file type, for read operations.
-
-NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data.
-
-Write operations to text files always result in appending to the existing file, so consecutive writes will increase
-the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to
-write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
-third argument.
- * `write_file('temp', 'text', 'foo', 'bar', 'baz')` or
- * write_file('temp', 'text', l('foo', 'bar', 'baz'))
+Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true`
+for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files 
+for folder listing.
  
-To log a single line of string
- 
-Supported values for resource `type` is:
+Supported values for resource `type` are:
  * `nbt` - NBT tag
  * `text` - text resource with automatic newlines added
  * `raw` - text resource without implied newlines
- * `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above
+ * `folder` - for `list_files` only - indicting folder listing instead of files
+ * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
  
 NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, 
 stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be
 sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each
-record to the file.
+record to the file. Since files are closed after each write, sending multiple lines of data to
+write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
+third argument.
 
 
-write_file('foo', 'shared_text, l('one', 'two'));
+write_file('foo', 'shared_text, ['one', 'two']);
 write_file('foo', 'shared_text', 'three\n', 'four\n');
 write_file('foo', 'shared_raw', 'five\n', 'six\n');
 

From 4b0fd8296c818a692fea3a67fe2ba73f3c747ae3 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 3 Dec 2020 22:40:16 -0400
Subject: [PATCH 19/64] player selects hotbar slot, fixes #557

---
 src/main/java/carpet/commands/PlayerCommand.java         | 3 +++
 src/main/java/carpet/helpers/EntityPlayerActionPack.java | 7 +++++++
 2 files changed, 10 insertions(+)

diff --git a/src/main/java/carpet/commands/PlayerCommand.java b/src/main/java/carpet/commands/PlayerCommand.java
index 0414acb4..9aa9297b 100644
--- a/src/main/java/carpet/commands/PlayerCommand.java
+++ b/src/main/java/carpet/commands/PlayerCommand.java
@@ -58,6 +58,9 @@ public class PlayerCommand
                         .then(makeActionCommand("dropStack", EntityPlayerActionPack.ActionType.DROP_STACK))
                         .then(makeDropCommand("dropStack", true))
                         .then(makeActionCommand("swapHands", EntityPlayerActionPack.ActionType.SWAP_HANDS))
+                        .then(literal("hotbar")
+                                .then(argument("slot", IntegerArgumentType.integer(1, 9))
+                                        .executes(c -> manipulate(c, ap -> ap.setSlot(IntegerArgumentType.getInteger(c, "slot"))))))
                         .then(literal("kill").executes(PlayerCommand::kill))
                         .then(literal("shadow"). executes(PlayerCommand::shadow))
                         .then(literal("mount").executes(manipulation(ap -> ap.mount(true)))
diff --git a/src/main/java/carpet/helpers/EntityPlayerActionPack.java b/src/main/java/carpet/helpers/EntityPlayerActionPack.java
index 92220866..0c1d8253 100644
--- a/src/main/java/carpet/helpers/EntityPlayerActionPack.java
+++ b/src/main/java/carpet/helpers/EntityPlayerActionPack.java
@@ -10,6 +10,7 @@ import net.minecraft.entity.player.PlayerInventory;
 import net.minecraft.entity.vehicle.BoatEntity;
 import net.minecraft.entity.vehicle.MinecartEntity;
 import net.minecraft.item.ItemStack;
+import net.minecraft.network.packet.s2c.play.HeldItemChangeS2CPacket;
 import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
 import net.minecraft.server.world.ServerWorld;
@@ -276,6 +277,12 @@ public class EntityPlayerActionPack
         }
     }
 
+    public void setSlot(int slot)
+    {
+        player.inventory.selectedSlot = slot-1;
+        player.networkHandler.sendPacket(new HeldItemChangeS2CPacket(slot-1));
+    }
+
     public enum ActionType
     {
         USE(true)

From 0c604593bb27fa7b86690a2646ef8d54fc811cd5 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Thu, 3 Dec 2020 23:30:42 -0400
Subject: [PATCH 20/64] added ability to render shapes around any entity

---
 .../assets/carpet/scripts/overlay.sc          | 62 +++++++++----------
 1 file changed, 28 insertions(+), 34 deletions(-)

diff --git a/src/main/resources/assets/carpet/scripts/overlay.sc b/src/main/resources/assets/carpet/scripts/overlay.sc
index a78aa78f..ed05761e 100644
--- a/src/main/resources/assets/carpet/scripts/overlay.sc
+++ b/src/main/resources/assets/carpet/scripts/overlay.sc
@@ -36,22 +36,15 @@ __config() ->
         'slime_chunks' -> ['__toggle', 'slime_chunks', 'chunks'],
         'portal coordinates' -> ['__toggle', 'coords', 'portals'],
         'portal links' -> ['__toggle', 'links', 'portals'],
-        '  following ' -> ['display_shape', [null,0xffffffff], true],
-        '  at ' -> ['display_shape', [null,0xffffffff], false],
-        '  following  ' -> ['display_shape', true],
-        '  at  ' -> ['display_shape', false],
+        '  following ' -> ['display_shape', [null,0xffffffff], true],
+        '  at ' -> ['display_shape', [null,0xffffffff], false],
+        '  following  ' -> ['display_shape', true],
+        '  at  ' -> ['display_shape', false],
         ' clear' -> 'clear_shape',
         'clear' -> 'clear',
     },
     'arguments' -> {
-        'structure' -> {'type' -> 'term', 'suggest' -> [
-            'monument', 'fortress', 'mansion', 'jungle_pyramid',
-            'desert_pyramid', 'endcity', 'igloo', 'shipwreck',
-            'swamp_hut', 'stronghold', 'ocean_ruin', 'buried_treasure',
-            'pillager_outpost', 'mineshaft', 'village', 'nether_fossil',
-            'bastion_remnant', 'ruined_portal'
-        ]},
-        'player' -> {'type' -> 'players', 'single' -> true},
+        'structure' -> {'type' -> 'term', 'suggest' -> plop():'structures' },
         'radius' -> {'type' -> 'int', 'min' -> 0, 'max' -> 1024, 'suggest' -> [128, 24, 32]},
         'shape' -> {'type' -> 'term', 'options' -> keys(global_shapes) },
         'color' -> {'type' -> 'teamcolor'}
@@ -59,29 +52,30 @@ __config() ->
 };
 
 
-display_shape(shape, r, player, color, following) ->
+display_shape(shape, r, entities, color, following) ->
 (
-
-    player = player(player);
-    if (!player, return());
-    if (shape == 'sphere',
-        shape_config = {
-            'center' -> if(following, [0,0,0], pos(player)),
-            'radius' -> r
-        }
-    , shape == 'box',
-        shape_config = {
-            'from' -> if(following, [-r,-r,-r], pos(player)-r),
-            'to' -> if(following, [r,r,r], pos(player)+r)
-        }
-    );
-    if (shape_config,
-        if (following, shape_config:'follow' = player);
-        shape_config:'fill' = color:1 - 200;
-        shape_config:'color' = color:1;
-        shape_config:'line' = 5;
-        draw_shape(shape, 72000, shape_config);
-        global_shapes:shape += shape_config;
+    thicc = max(1, floor(6-length(entities)/2));
+    fillc = 250 - 100/(length(entities)+1);
+    for (entities,
+        if (shape == 'sphere',
+            shape_config = {
+                'center' -> if(following, [0,0,0], pos(_)),
+                'radius' -> r
+            }
+        , shape == 'box',
+            shape_config = {
+                'from' -> if(following, [-r,-r,-r], pos(_)-r),
+                'to' -> if(following, [r,r,r], pos(_)+r)
+            }
+        );
+        if (shape_config,
+            if (following, shape_config:'follow' = _);
+            shape_config:'fill' = color:1 - fillc;
+            shape_config:'color' = color:1;
+            shape_config:'line' = thicc;
+            draw_shape(shape, 72000, shape_config);
+            global_shapes:shape += shape_config;
+        )
     )
 );
 

From 59b752e6c9fdffdc58521cfd26528b38a0612cc8 Mon Sep 17 00:00:00 2001
From: gnembon 
Date: Fri, 4 Dec 2020 22:02:01 -0400
Subject: [PATCH 21/64] better messaging on handling of unknown functions.
 fixes #606

---
 docs/scarpet/api/Auxiliary.md                 |  3 ++
 docs/scarpet/api/Entities.md                  |  4 ++
 docs/scarpet/api/Events.md                    |  4 ++
 .../language/FunctionsAndControlFlow.md       | 53 ++++++++++++++++++-
 docs/scarpet/language/SystemFunctions.md      |  3 ++
 src/main/java/carpet/script/ScriptHost.java   | 11 ++--
 6 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md
index 6362b82d..af470d08 100644
--- a/docs/scarpet/api/Auxiliary.md
+++ b/docs/scarpet/api/Auxiliary.md
@@ -444,6 +444,9 @@ algorithm has taken in to account last time mobs spawned.
 Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end 
 of the tick, and they will run in order they were scheduled.
 
+In case you want to schedule a function that is not defined in your module, please read the tips on
+ "Passing function references to other modules of your application" section in the `call(...)` section.
+
 ### `statistic(player, category, entry)`
 
 Queries in-game statistics for certain values. Categories include:
diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md
index e73c0697..844eedd7 100644
--- a/docs/scarpet/api/Entities.md
+++ b/docs/scarpet/api/Entities.md
@@ -746,6 +746,10 @@ Required arguments: `entity, amount, source, attacking_entity`
 It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error 
 when you attach an inapplicable event to it.
 
+In case you want to pass an event handler that is not defined in your module, please read the tips on
+ "Passing function references to other modules of your application" section in the `call(...)` section.
+
+
 ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
 
 Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md
index e64aad1f..b337090d 100644
--- a/docs/scarpet/api/Events.md
+++ b/docs/scarpet/api/Events.md
@@ -263,6 +263,10 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event
 handle_event('tick', null)  // nah, ima good, kthxbai
 
+In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + ### `signal_event(event, target_player?, ... args?)` Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 2d6a8d00..3d0ad0db 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -135,7 +135,7 @@ foo(... x) -> ... # all arguments for foo are included in the list ### `import(module_name, symbols ...)` Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use -them in the current app. This include other symbols imported by these modules. Scarpet supports cicular dependencies, +them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies, but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. Returns full list of available symbols that could be imported from this module, which can be used to debug import issues, and list contents of libraries. @@ -144,9 +144,58 @@ issues, and list contents of libraries. calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly except you can use it with function value, or name instead. This means you can pass functions to other user defined -functions as arguments and call them with `call` internally. And since function definitions return the defined +functions as arguments and call them with `call` internally. Since function definitions return the defined function, they can be defined in place as anonymous functions. +#### Passing function references to other modules of your application + +In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports, +meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the +library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app, +it should import back that method from the main module for visibility. + +Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in +the parent app: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme('foo' , 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` + +In this case `'foo'` will not dereferenced in `lib` is not visible by name. In tightly coupled modules, where `lib` is just +a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like +imports in Java classes), and that solves the issue but makes the library dependent on the main app: +``` +//lib.scl +import('app','foo'); +callme(fun, arg) -> call(fun, arg); +``` +But you can circumvent that issue by explictly dereferencing the local function where it is used as a lambda argument created +in the module in which the requested function is visible: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme(_(x) -> foo(x), 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` +Or passing an explicit reference to the function, not calling it by name: +``` +//app.sc +import('lib', 'callme'); +global_foohandler = (foo(x) -> x*x); +test() -> callme(global_foohandler, 5); +``` + + Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for programmers with their own lambda arguments diff --git a/docs/scarpet/language/SystemFunctions.md b/docs/scarpet/language/SystemFunctions.md index 0959f243..1f3768e6 100644 --- a/docs/scarpet/language/SystemFunctions.md +++ b/docs/scarpet/language/SystemFunctions.md @@ -122,6 +122,9 @@ a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5) => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
+In case you want to create a task based on a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + ### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index b494c9de..d984673f 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -18,13 +18,10 @@ import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Predicate; -import java.util.stream.Collectors; import java.util.stream.Stream; public abstract class ScriptHost @@ -189,7 +186,13 @@ public abstract class ScriptHost public FunctionValue getAssertFunction(Module module, String name) { FunctionValue ret = getFunction(module, name); - if (ret == null) throw new InternalExpressionException("Function "+name+" is not defined yet"); + if (ret == null) + { + if (module == main) + throw new InternalExpressionException("Function '"+name+"' is not defined yet"); + else + throw new InternalExpressionException("Function '"+name+"' is not defined nor visible by its name in the imported module '"+module.getName()+"'"); + } return ret; } private FunctionValue getFunction(Module module, String name) From 063a1735a0c5af86c84a5b740e40063b3c18247d Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 4 Dec 2020 22:10:15 -0400 Subject: [PATCH 22/64] wording --- docs/scarpet/language/FunctionsAndControlFlow.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/scarpet/language/FunctionsAndControlFlow.md b/docs/scarpet/language/FunctionsAndControlFlow.md index 3d0ad0db..3d11e8bf 100644 --- a/docs/scarpet/language/FunctionsAndControlFlow.md +++ b/docs/scarpet/language/FunctionsAndControlFlow.md @@ -167,7 +167,7 @@ test() -> callme('foo' , 5); callme(fun, arg) -> call(fun, arg); ``` -In this case `'foo'` will not dereferenced in `lib` is not visible by name. In tightly coupled modules, where `lib` is just +In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like imports in Java classes), and that solves the issue but makes the library dependent on the main app: ``` @@ -175,7 +175,7 @@ imports in Java classes), and that solves the issue but makes the library depend import('app','foo'); callme(fun, arg) -> call(fun, arg); ``` -But you can circumvent that issue by explictly dereferencing the local function where it is used as a lambda argument created +You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created in the module in which the requested function is visible: ``` //app.sc @@ -187,7 +187,7 @@ test() -> callme(_(x) -> foo(x), 5); //lib.scl callme(fun, arg) -> call(fun, arg); ``` -Or passing an explicit reference to the function, not calling it by name: +Or by passing an explicit reference to the function, instead of calling it by name: ``` //app.sc import('lib', 'callme'); @@ -195,7 +195,6 @@ global_foohandler = (foo(x) -> x*x); test() -> callme(global_foohandler, 5); ``` - Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for programmers with their own lambda arguments From 8bf1bf6b4189170da8e3148283170d91d9e09f52 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 5 Dec 2020 03:22:18 +0000 Subject: [PATCH 23/64] Merge docs for 'Merge pull request #609 from gnembon/module_imports better messaging on handling of unknown functions. fixes #606' --- docs/scarpet/Full.md | 66 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index bd8c1ae3..0d731ea7 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -804,6 +804,9 @@ a = 3; task_thread('temp', _(outer(a), b) -> foo(a,b), 5) => Another example of running the same thing passing arguments using closure over anonymous function as well as passing a parameter.
+In case you want to create a task based on a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + ### `sleep()` `sleep(timeout)`, `sleep(timeout, close_expr)` @@ -1373,7 +1376,7 @@ foo(... x) -> ... # all arguments for foo are included in the list ### `import(module_name, symbols ...)` Imports symbols from other apps and libraries into the current one: global variables or functions, allowing to use -them in the current app. This include other symbols imported by these modules. Scarpet supports cicular dependencies, +them in the current app. This includes other symbols imported by these modules. Scarpet supports circular dependencies, but if symbols are used directly in the module body rather than functions, it may not be able to retrieve them. Returns full list of available symbols that could be imported from this module, which can be used to debug import issues, and list contents of libraries. @@ -1382,9 +1385,57 @@ issues, and list contents of libraries. calls a user defined function with specified arguments. It is equivalent to calling `function(args...)` directly except you can use it with function value, or name instead. This means you can pass functions to other user defined -functions as arguments and call them with `call` internally. And since function definitions return the defined +functions as arguments and call them with `call` internally. Since function definitions return the defined function, they can be defined in place as anonymous functions. +#### Passing function references to other modules of your application + +In case a function is defined by its name, Scarpet will attempt to resolve its definition in the given module and its imports, +meaning if the call is in a imported library, and not in the main module of your app, and that function is not visible from the +library perspective, but in the app, it won't be call-able. In case you pass a function name to a separate module in your app, +it should import back that method from the main module for visibility. + +Check an example of a problematic code of a library that expects a function value as a passed argument and how it is called in +the parent app: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme('foo' , 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` + +In this case `'foo'` will fail to dereference in `lib` as it is not visible by name. In tightly coupled modules, where `lib` is just +a component of your `app` you can use circular import to acknowledge the symbol from the other module (pretty much like +imports in Java classes), and that solves the issue but makes the library dependent on the main app: +``` +//lib.scl +import('app','foo'); +callme(fun, arg) -> call(fun, arg); +``` +You can circumvent that issue by explicitly dereferencing the local function where it is used as a lambda argument created +in the module in which the requested function is visible: +``` +//app.sc +import('lib', 'callme'); +foo(x) -> x*x; +test() -> callme(_(x) -> foo(x), 5); +``` +``` +//lib.scl +callme(fun, arg) -> call(fun, arg); +``` +Or by passing an explicit reference to the function, instead of calling it by name: +``` +//app.sc +import('lib', 'callme'); +global_foohandler = (foo(x) -> x*x); +test() -> callme(global_foohandler, 5); +``` + Little technical note: the use of `_` in expression passed to built in functions is much more efficient due to not creating new call stacks for each invoked function, but anonymous functions is the only mechanism available for programmers with their own lambda arguments @@ -3550,6 +3601,10 @@ Required arguments: `entity, amount, source, attacking_entity` It doesn't mean that all entity types will have a chance to execute a given event, but entities will not error when you attach an inapplicable event to it. +In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle @@ -4075,6 +4130,10 @@ handle_event('tick', _() -> foo('tick happened')); // built-in event handle_event('tick', null) // nah, ima good, kthxbai
+In case you want to pass an event handler that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + + ### `signal_event(event, target_player?, ... args?)` Fires a specific event. If the event does not exist (only `handle_event` creates missing new events), or provided argument list @@ -4740,6 +4799,9 @@ algorithm has taken in to account last time mobs spawned. Schedules a user defined function to run with a specified `delay` ticks of delay. Scheduled functions run at the end of the tick, and they will run in order they were scheduled. +In case you want to schedule a function that is not defined in your module, please read the tips on + "Passing function references to other modules of your application" section in the `call(...)` section. + ### `statistic(player, category, entry)` Queries in-game statistics for certain values. Categories include: From 4a67668c8c394f869f41148ddc450a6c3ddfe0c8 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 5 Dec 2020 01:58:27 -0400 Subject: [PATCH 24/64] proper mocked command source. Fixes #608 --- .../java/carpet/script/api/Auxiliary.java | 20 +- .../script/utils/FixedCommandSource.java | 206 ++++++++++++++++++ 2 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 src/main/java/carpet/script/utils/FixedCommandSource.java diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 1c8503ef..5f417d4c 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -18,6 +18,7 @@ import carpet.script.argument.FunctionArgument; import carpet.script.argument.Vector3Argument; import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; +import carpet.script.utils.FixedCommandSource; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; import carpet.script.value.EntityValue; @@ -565,29 +566,14 @@ public class Auxiliary { expression.addLazyFunction("run", 1, (c, t, lv) -> { BlockPos target = ((CarpetContext)c).origin; - Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D); + Vec3d posf = new Vec3d((double)target.getX()+0.5D, target.getY(),(double)target.getZ()+0.5D); ServerCommandSource s = ((CarpetContext)c).s; try { Value[] error = {Value.NULL}; List output = new ArrayList<>(); Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute( - new ServerCommandSource( - CommandOutput.DUMMY, posf, Vec2f.ZERO, s.getWorld(), CarpetSettings.runPermissionLevel, - s.getName(), s.getDisplayName(), s.getMinecraftServer(), s.getEntity(), true, - (ctx, succ, res) -> { }, EntityAnchorArgumentType.EntityAnchor.FEET) - { - @Override - public void sendError(Text message) - { - error[0] = new FormattedTextValue(message); - } - @Override - public void sendFeedback(Text message, boolean broadcastToOps) - { - output.add(new FormattedTextValue(message)); - } - }, + new FixedCommandSource(s, posf, error, output), lv.get(0).evalValue(c).getString()) ); Value ret = ListValue.of(retval, ListValue.wrap(output), error[0]); diff --git a/src/main/java/carpet/script/utils/FixedCommandSource.java b/src/main/java/carpet/script/utils/FixedCommandSource.java new file mode 100644 index 00000000..c88e9874 --- /dev/null +++ b/src/main/java/carpet/script/utils/FixedCommandSource.java @@ -0,0 +1,206 @@ +package carpet.script.utils; + +import carpet.CarpetSettings; +import carpet.script.value.FormattedTextValue; +import carpet.script.value.Value; +import com.mojang.brigadier.ResultConsumer; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.command.argument.EntityAnchorArgumentType; +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.Text; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; + +import java.util.List; +import java.util.function.BinaryOperator; + +public class FixedCommandSource extends ServerCommandSource +{ + private Vec3d position; + private ServerWorld world; + private final String simpleName; + private final Text name; + private final MinecraftServer server; + private Entity entity; + private ResultConsumer resultConsumer; + private EntityAnchorArgumentType.EntityAnchor entityAnchor; + private Vec2f rotation; + private final Value[] error; + private final List chatOutput; + + public FixedCommandSource(ServerCommandSource original, Vec3d pos, Value[] error, List chatOutput) + { + super(CommandOutput.DUMMY, pos, Vec2f.ZERO, original.getWorld(), CarpetSettings.runPermissionLevel, + original.getName(), original.getDisplayName(), original.getMinecraftServer(), original.getEntity(), true, + (ctx, succ, res) -> { }, EntityAnchorArgumentType.EntityAnchor.FEET); + position = pos; + world = original.getWorld(); + simpleName = original.getName(); + name = original.getDisplayName(); + server = original.getMinecraftServer(); + entity = original.getEntity(); + resultConsumer = (ctx, succ, res) -> { }; + entityAnchor = original.getEntityAnchor(); + rotation = Vec2f.ZERO; + this.error = error; + this.chatOutput = chatOutput; + } + + @Override + public ServerCommandSource withEntity(Entity entity) + { + this.entity = entity; + return this; + } + + @Override + public ServerCommandSource withPosition(Vec3d position) + { + this.position = position; + return this; + } + + @Override + public ServerCommandSource withRotation(Vec2f rotation) + { + this.rotation = rotation; + return this; + } + + @Override + public ServerCommandSource withConsumer(ResultConsumer consumer) + { + this.resultConsumer = consumer; + return this; + } + + @Override + public ServerCommandSource mergeConsumers(ResultConsumer consumer, BinaryOperator> binaryOperator) + { + return this.withConsumer(binaryOperator.apply(this.resultConsumer, consumer)); + } + + @Override + public ServerCommandSource withSilent() + { + return this; + } + + @Override + public ServerCommandSource withLevel(int level) + { + return this; + } + + @Override + public ServerCommandSource withMaxLevel(int level) + { + return this; + } + + @Override + public ServerCommandSource withEntityAnchor(EntityAnchorArgumentType.EntityAnchor anchor) + { + entityAnchor = anchor; + return this; + } + + @Override + public ServerCommandSource withWorld(ServerWorld world) + { + this.world = world; + return this; + } + + @Override + public Text getDisplayName() + { + return name; + } + + @Override + public String getName() + { + return simpleName; + } + + @Override + public Vec3d getPosition() + { + return position; + } + + @Override + public ServerWorld getWorld() + { + return world; + } + + @Override + public Entity getEntity() + { + return entity; + } + + @Override + public Entity getEntityOrThrow() throws CommandSyntaxException + { + if (entity == null) { + throw REQUIRES_ENTITY_EXCEPTION.create(); + } else { + return entity; + } + } + + @Override + public ServerPlayerEntity getPlayer() throws CommandSyntaxException + { + if (!(this.entity instanceof ServerPlayerEntity)) { + throw REQUIRES_PLAYER_EXCEPTION.create(); + } else { + return (ServerPlayerEntity)this.entity; + } + } + + @Override + public Vec2f getRotation() + { + return rotation; + } + + @Override + public MinecraftServer getMinecraftServer() + { + return server; + } + + @Override + public EntityAnchorArgumentType.EntityAnchor getEntityAnchor() + { + return entityAnchor; + } + + + @Override + public void onCommandComplete(CommandContext context, boolean success, int result) + { + } + + @Override + public void sendError(Text message) + { + error[0] = new FormattedTextValue(message); + } + @Override + public void sendFeedback(Text message, boolean broadcastToOps) + { + chatOutput.add(new FormattedTextValue(message)); + } + +} From 1e1b4c5406851ceca4bbf59a1f5a9f9cd31f3e0a Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 5 Dec 2020 21:56:37 -0400 Subject: [PATCH 25/64] fix wording of docs --- docs/scarpet/api/BlockIterations.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/scarpet/api/BlockIterations.md b/docs/scarpet/api/BlockIterations.md index 29d39cf3..1a5e6091 100644 --- a/docs/scarpet/api/BlockIterations.md +++ b/docs/scarpet/api/BlockIterations.md @@ -2,14 +2,16 @@ These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. -### `scan(center, range, lower_range?, expr)` +### `scan(center, range, upper_range?, expr)` Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in -positive values. -`center` can be defined either as three coordinates, a list of three coords, or a block value. -`range` and `lower_range` can have the same representations, just if its a block, it computes the distance to the center +positive values, so you can use that if you know the lower coord, and dimension by calling `'scan(center, 0, 0, 0, w, h, d, ...)`. + +`center` can be defined either as three coordinates, a single tuple of three coords, or a block value. +`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center as range instead of taking the values as is. + `expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself. Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context, @@ -34,7 +36,7 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
-### `rect(centre, range?, positive_range?)` +### `rect(centre, range?, upper_range?)` Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective @@ -42,7 +44,7 @@ number of blocks in each direction. If `positive_range` arguments are specified, it uses `range` for negative offset, and `positive_range` for positive. `centre` can be defined either as three coordinates, a list of three coords, or a block value. -`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center +`range` and `positive_range` can have the same representations, just if they are block values, it computes the distance to the center as range instead of taking the values as is.` ### `diamond(centre_pos, radius?, height?)` From 3318d15dabfbd5187645f02389ef3cf3ea8a54fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Dec 2020 01:57:03 +0000 Subject: [PATCH 26/64] Merge docs for 'fix wording of docs' --- docs/scarpet/Full.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 0d731ea7..66269f15 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2805,14 +2805,16 @@ the future. These functions help scan larger areas of blocks without using generic loop functions, like nested `loop`. -### `scan(center, range, lower_range?, expr)` +### `scan(center, range, upper_range?, expr)` Evaluates expression over area of blocks defined by its center `center = (cx, cy, cz)`, expanded in all directions by `range = (dx, dy, dz)` blocks, or optionally in negative with `range` coords, and `upper_range` coords in -positive values. -`center` can be defined either as three coordinates, a list of three coords, or a block value. -`range` and `lower_range` can have the same representations, just if its a block, it computes the distance to the center +positive values, so you can use that if you know the lower coord, and dimension by calling `'scan(center, 0, 0, 0, w, h, d, ...)`. + +`center` can be defined either as three coordinates, a single tuple of three coords, or a block value. +`range` and `upper_range` can have the same representations, just if they are block values, it computes the distance to the center as range instead of taking the values as is. + `expr` receives `_x, _y, _z` as coords of current analyzed block and `_`, which represents the block itself. Returns number of successful evaluations of `expr` (with `true` boolean result) unless called in void context, @@ -2837,7 +2839,7 @@ Returns the list of 6 neighbouring blocks to the argument. Commonly used with ot for(neighbours(x,y,z),air(_)) => 4 // number of air blocks around a block
-### `rect(centre, range?, positive_range?)` +### `rect(centre, range?, upper_range?)` Returns an iterator, just like `range` function that iterates over a rectangular area of blocks. If only center point is specified, it iterates over 27 blocks. If `range` arguments are specified, expands selection by the respective @@ -2845,7 +2847,7 @@ number of blocks in each direction. If `positive_range` arguments are specified, it uses `range` for negative offset, and `positive_range` for positive. `centre` can be defined either as three coordinates, a list of three coords, or a block value. -`range` and `positive_range` can have the same representations, just if its a block, it computes the distance to the center +`range` and `positive_range` can have the same representations, just if they are block values, it computes the distance to the center as range instead of taking the values as is.` ### `diamond(centre_pos, radius?, height?)` From 28e84dd64e6d37584b915f6d5fd385648c4e7833 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sat, 5 Dec 2020 22:41:15 -0400 Subject: [PATCH 27/64] entity_area can accept entity as a starting bounding box. --- docs/scarpet/api/Entities.md | 7 +++++-- .../java/carpet/script/api/Auxiliary.java | 2 +- src/main/java/carpet/script/api/Entities.java | 18 +++++++++++++---- .../script/argument/Vector3Argument.java | 20 ++++++++++++++++--- .../assets/carpet/scripts/ai_tracker.sc | 6 ++---- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 844eedd7..f6219452 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -74,10 +74,13 @@ belonging to that group. Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from -the center point. Uses the same `type` selectors as `entities_list`. +the center point/area. Uses the same `type` selectors as `entities_list`. `center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can -also be represented as a block, in this case the search box will be centered on the middle of the block. +also be represented as a block, in this case the search box will be centered on the middle of the block, or an entity - in this case +entire bounding box of the entity serves as a 'center' of search which is then expanded in all directions with the `'distance'` vector. + +In any case - returns all entities which bounding box collides with the bounding box defined by `'center'` and `'disteance'`. entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and cuboid search area. diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 5f417d4c..227cc386 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -332,7 +332,7 @@ public class Auxiliary { { Value nameValue = lv.get(0).evalValue(c); name = nameValue instanceof NullValue ? "" : nameValue.getString(); - pointLocator = Vector3Argument.findIn(cc, lv, 1, true); + pointLocator = Vector3Argument.findIn(cc, lv, 1, true, false); if (lv.size()>pointLocator.offset) { BlockArgument blockLocator = BlockArgument.findIn(cc, lv, pointLocator.offset, true, true, false); diff --git a/src/main/java/carpet/script/api/Entities.java b/src/main/java/carpet/script/api/Entities.java index 7fac7ef1..c5a137a9 100644 --- a/src/main/java/carpet/script/api/Entities.java +++ b/src/main/java/carpet/script/api/Entities.java @@ -194,14 +194,24 @@ public class Entities { if (lv.size()<3) throw new InternalExpressionException("'entity_area' requires entity type, center and range arguments"); String who = lv.get(0).evalValue(c).getString(); CarpetContext cc = (CarpetContext)c; - Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1); - Vec3d center = centerLocator.vec; - if (centerLocator.fromBlock) center.add(0.5, 0.5, 0.5); + Vector3Argument centerLocator = Vector3Argument.findIn(cc, lv, 1, false, true); + + Box centerBox; + if (centerLocator.entity != null) + { + centerBox = centerLocator.entity.getBoundingBox(); + } + else + { + Vec3d center = centerLocator.vec; + if (centerLocator.fromBlock) center.add(0.5, 0.5, 0.5); + centerBox = new Box(center, center); + } Vector3Argument rangeLocator = Vector3Argument.findIn(cc, lv, centerLocator.offset); if (rangeLocator.fromBlock) throw new InternalExpressionException("Range of 'entity_area' cannot come from a block argument"); Vec3d range = rangeLocator.vec; - Box area = new Box(center, center).expand(range.x, range.y, range.z); + Box area = centerBox.expand(range.x, range.y, range.z); EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who); List entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); diff --git a/src/main/java/carpet/script/argument/Vector3Argument.java b/src/main/java/carpet/script/argument/Vector3Argument.java index 6c408089..003ffc4c 100644 --- a/src/main/java/carpet/script/argument/Vector3Argument.java +++ b/src/main/java/carpet/script/argument/Vector3Argument.java @@ -4,9 +4,11 @@ import carpet.script.CarpetContext; import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import carpet.script.value.BlockValue; +import carpet.script.value.EntityValue; import carpet.script.value.ListValue; import carpet.script.value.NumericValue; import carpet.script.value.Value; +import net.minecraft.entity.Entity; import net.minecraft.util.math.Vec3d; import java.util.List; @@ -16,7 +18,8 @@ public class Vector3Argument extends Argument public Vec3d vec; public final double yaw; public final double pitch; - public boolean fromBlock; + public boolean fromBlock = false; + public Entity entity = null; private Vector3Argument(Vec3d v, int o) { super(o); @@ -38,13 +41,19 @@ public class Vector3Argument extends Argument return this; } + private Vector3Argument withEntity(Entity e) + { + entity = e; + return this; + } + public static Vector3Argument findIn(CarpetContext c, List params, int offset) { - return findIn(c,params, offset, false); + return findIn(c,params, offset, false, false); } - public static Vector3Argument findIn(CarpetContext c, List params, int offset, boolean optionalDirection) + public static Vector3Argument findIn(CarpetContext c, List params, int offset, boolean optionalDirection, boolean optionalEntity) { try { @@ -54,6 +63,11 @@ public class Vector3Argument extends Argument // pos + 0.5v return (new Vector3Argument(Vec3d.ofCenter(((BlockValue) v1).getPos()), 1+offset)).fromBlock(); } + if (optionalEntity && v1 instanceof EntityValue) + { + Entity e = ((EntityValue) v1).getEntity(); + return new Vector3Argument(e.getPos(), 1+offset).withEntity(e); + } if (v1 instanceof ListValue) { List args = ((ListValue) v1).getItems(); diff --git a/src/main/resources/assets/carpet/scripts/ai_tracker.sc b/src/main/resources/assets/carpet/scripts/ai_tracker.sc index 4e7e9fd0..2e1977e3 100644 --- a/src/main/resources/assets/carpet/scripts/ai_tracker.sc +++ b/src/main/resources/assets/carpet/scripts/ai_tracker.sc @@ -145,9 +145,8 @@ global_functions = { [10+half_width,10+height,10+half_width], 0x65432100, 'buddy detection', false ); - [x,y,z] = pos(e); current_id = e~'id'; - buddies = entity_area('villager', x, y+height/2, z, 10+half_width, 10+height/2, 10+half_width); + buddies = entity_area('villager', e, 10, 10, 10); nb = length(buddies); for (filter(buddies, _~'id' != current_id), visuals+=['line', global_duration, 'from', pos(e), 'to', pos(_), 'color', 0xffff00ff]; @@ -518,9 +517,8 @@ __tick_tracker() -> return() ); p = player(); - [px, py, pz] = pos(p); in_dimension(p, - for (entity_area('valid', px, py, pz, global_range, global_range, global_range), + for (entity_area('valid', p, global_range, global_range, global_range), __handle_entity(_) ) ); From 8acb3227476d5e2c807493dc59d87595f22f9417 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Dec 2020 02:41:44 +0000 Subject: [PATCH 28/64] Merge docs for 'entity_area can accept entity as a starting bounding box.' --- docs/scarpet/Full.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 66269f15..7e34a261 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2931,10 +2931,13 @@ belonging to that group. Returns entities of a specified type in an area centered on `center` and at most `distance` blocks away from -the center point. Uses the same `type` selectors as `entities_list`. +the center point/area. Uses the same `type` selectors as `entities_list`. `center` and `distance` can either be a triple of coordinates or three consecutive arguments for `entity_area`. `center` can -also be represented as a block, in this case the search box will be centered on the middle of the block. +also be represented as a block, in this case the search box will be centered on the middle of the block, or an entity - in this case +entire bounding box of the entity serves as a 'center' of search which is then expanded in all directions with the `'distance'` vector. + +In any case - returns all entities which bounding box collides with the bounding box defined by `'center'` and `'disteance'`. entity_area is simpler than `entity_selector` and runs about 20% faster, but is limited to predefined selectors and cuboid search area. From 42e67f27b75245e6a63f9a85d1639425f0c22899 Mon Sep 17 00:00:00 2001 From: Ghoulboy Date: Sun, 6 Dec 2020 21:03:15 +0100 Subject: [PATCH 29/64] Adding absorption and xp API, and weather control (#540) * fixes #537 * fixes #547 --- docs/scarpet/api/Auxiliary.md | 3 +- docs/scarpet/api/BlocksAndWorldAccess.md | 10 ++++ docs/scarpet/api/Entities.md | 23 +++++++ .../carpet/fakes/ServerWorldInterface.java | 7 +++ ...xin.java => ServerWorld_scarpetMixin.java} | 10 +++- .../java/carpet/script/api/Auxiliary.java | 46 +++++++++----- .../java/carpet/script/api/WorldAccess.java | 60 +++++++++++++++++++ .../java/carpet/script/value/EntityValue.java | 43 ++++++++++++- src/main/resources/carpet.mixins.json | 2 +- 9 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 src/main/java/carpet/fakes/ServerWorldInterface.java rename src/main/java/carpet/mixins/{ServerWorld_scarpetEventMixin.java => ServerWorld_scarpetMixin.java} (86%) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index af470d08..10544321 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -193,9 +193,10 @@ Other value types will only be converted to tags (including NBT tags) if `force` extra treatment when loading them back from NBT, but using `force` true will always produce output / never produce an exception. -### `print(expr)` +### `print(expr)`, `print(player/player_list, expr)` Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. +Can optionally define player or list of players to send the message to. ### `format(components, ...)`, `format(l(components, ...))` diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 8f295ee0..990ae027 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -212,6 +212,16 @@ Causes a block to be harvested by a specified player entity. Honors player item tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break that block, a block doesn't get destroyed either. +### `weather()`,`weather(type)`,`weather(type, ticks)` + +If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will +always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather. + +With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type. +NB: It can thunder without there being a thunderstorm, there has to be both rain and thunder to form a storm. + +With two args, sets the weather to `type` for `ticks` ticks. + ## Block and World querying ### `pos(block), pos(entity)` diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index f6219452..1d053fbf 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -396,6 +396,18 @@ Number indicating remaining entity health, or `null` if not applicable. Retrieves player hunger related information. For non-players, returns `null`. +### `query(e, 'absorption')` + +Gets the absorption of the player (yellow hearts, e.g when having a golden apple.) + +### `query(e,'xp')` +### `query(e,'xp_level')` +### `query(e,'xp_progress')` +### `query(e,'score')` + +Numbers related to player's xp. `xp` is the overall xp in the bar, `xp_level` is the levels seen in the hotbar, +`xp_points` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the score displayed upon death + ### `query(e, 'air')` Number indicating remaining entity health, or `null` if not applicable. @@ -706,6 +718,17 @@ Will set entity on fire for `ticks` ticks. Set to 0 to extinguish. Modifies directly player raw hunger components. Has no effect on non-players +### `modify(e, 'absorption', value)` + +Sets the absorption value for the player. Each point is half a yellow heart. + +### `modify(e, 'add_xp', value)` +### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_score', value)` + +Manipulates player xp values - that's the method you probably want to use +to manipulate how much 'xp' an action should give. + ### `modify(e, 'air', ticks)` Modifies entity air diff --git a/src/main/java/carpet/fakes/ServerWorldInterface.java b/src/main/java/carpet/fakes/ServerWorldInterface.java new file mode 100644 index 00000000..67414706 --- /dev/null +++ b/src/main/java/carpet/fakes/ServerWorldInterface.java @@ -0,0 +1,7 @@ +package carpet.fakes; + +import net.minecraft.world.level.ServerWorldProperties; + +public interface ServerWorldInterface { + ServerWorldProperties getWorldPropertiesCM(); +} diff --git a/src/main/java/carpet/mixins/ServerWorld_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java similarity index 86% rename from src/main/java/carpet/mixins/ServerWorld_scarpetEventMixin.java rename to src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java index 5c11338a..5e83e881 100644 --- a/src/main/java/carpet/mixins/ServerWorld_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/ServerWorld_scarpetMixin.java @@ -1,6 +1,7 @@ package carpet.mixins; import carpet.CarpetSettings; +import carpet.fakes.ServerWorldInterface; import carpet.script.CarpetEventServer; import net.minecraft.entity.Entity; import net.minecraft.entity.LightningEntity; @@ -9,7 +10,9 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.profiler.Profiler; import net.minecraft.world.chunk.WorldChunk; +import net.minecraft.world.level.ServerWorldProperties; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -19,7 +22,7 @@ import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD; import static carpet.script.CarpetEventServer.Event.LIGHTNING; @Mixin(ServerWorld.class) -public class ServerWorld_scarpetEventMixin +public class ServerWorld_scarpetMixin implements ServerWorldInterface { @Inject(method = "tickChunk", locals = LocalCapture.CAPTURE_FAILHARD, at = @At( value = "INVOKE", @@ -54,4 +57,9 @@ public class ServerWorld_scarpetEventMixin } } + @Shadow + private ServerWorldProperties worldProperties; + public ServerWorldProperties getWorldPropertiesCM(){ + return worldProperties; + } } diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 227cc386..ec5499c0 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -469,28 +469,42 @@ public class Auxiliary { if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments"); ServerCommandSource s = ((CarpetContext)c).s; Value res = lv.get(0).evalValue(c); - if (lv.size() == 2) - { - ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res); - if (player == null) return LazyValue.NULL; - s = player.getCommandSource(); + Value playersVal = Value.NULL; + if (lv.size() == 2){ + playersVal = res; res = lv.get(1).evalValue(c); } - if (res instanceof FormattedTextValue) - { - s.sendFeedback(((FormattedTextValue) res).getText(), false); - } - else - { - if (s.getEntity() instanceof PlayerEntity) - { - Messenger.m((PlayerEntity) s.getEntity(), "w " + res.getString()); + + if(playersVal instanceof ListValue){ + + List players = ((ListValue) playersVal).getItems(); + for(Value playerVal:players){ + ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), playerVal); + if (player == null) return LazyValue.NULL; + if(res instanceof FormattedTextValue) + player.getCommandSource().sendFeedback(((FormattedTextValue) res).getText(), false); + else + Messenger.m(player, "w "+res.getString()); } + + } else if(playersVal instanceof EntityValue){ + + ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), playersVal); + if (player == null) return LazyValue.NULL; + if(res instanceof FormattedTextValue) + player.getCommandSource().sendFeedback(((FormattedTextValue) res).getText(), false); + else + Messenger.m(player, "w "+res.getString()); + + } else { + + if (res instanceof FormattedTextValue) + s.sendFeedback(((FormattedTextValue) res).getText(), false); else - { Messenger.m(s, "w " + res.getString()); - } + } + Value finalRes = res; return (_c, _t) -> finalRes; // pass through for variables }); diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index fa7a6361..f1888417 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -6,6 +6,7 @@ import carpet.fakes.BiomeArrayInterface; import carpet.fakes.ChunkGeneratorInterface; import carpet.fakes.ChunkTicketManagerInterface; import carpet.fakes.ServerChunkManagerInterface; +import carpet.fakes.ServerWorldInterface; import carpet.fakes.SpawnHelperInnerInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.helpers.FeatureGenerator; @@ -58,6 +59,7 @@ import net.minecraft.item.SwordItem; import net.minecraft.item.TridentItem; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ChunkTicket; import net.minecraft.server.world.ChunkTicketType; @@ -89,6 +91,7 @@ import net.minecraft.world.gen.ChunkRandom; import net.minecraft.world.gen.chunk.ChunkGenerator; import net.minecraft.world.gen.feature.ConfiguredStructureFeature; import net.minecraft.world.gen.feature.StructureFeature; +import net.minecraft.world.level.ServerWorldProperties; import net.minecraft.world.poi.PointOfInterest; import net.minecraft.world.poi.PointOfInterestStorage; import net.minecraft.world.poi.PointOfInterestType; @@ -413,6 +416,63 @@ public class WorldAccess { }); + expression.addLazyFunction("weather",-1,(c, t, lv) -> { + ServerWorld world = ((CarpetContext) c).s.getWorld(); + + if(lv.size()==0)//cos it can thunder when raining or when clear. + return (_c,_t)->new StringValue( world.isThundering()? "thunder" : (world.isRaining()? "rain":"clear")); + + + Value weather = lv.get(0).evalValue(c); + ServerWorldProperties worldProperties = ((ServerWorldInterface) world).getWorldPropertiesCM(); + + if(lv.size()==1) { + int ticks; + + switch (weather.getString()) { + case "clear": + ticks = worldProperties.getClearWeatherTime(); + break; + case "rain": + ticks = world.isRaining()? worldProperties.getRainTime():0;//cos if not it gives 1 for some reason + break; + case "thunder": + ticks = world.isThundering()? worldProperties.getThunderTime():0;//same dealio here + break; + default: + throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); + } + return (_c,_t) -> new NumericValue(ticks); + } else if(lv.size()==2){ + int ticks = ((NumericValue)lv.get(1).evalValue(c)).getInt(); + + switch (weather.getString()){ + case "clear": + world.setWeather(ticks,0,false,false); + break; + + case "rain": + world.setWeather(0,ticks,true,false); + break; + + case "thunder": + world.setWeather( + 0, + ticks,//this is used to set thunder time, idk why... + true, + true + ); + break; + + default: + throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); + } + + return LazyValue.TRUE; + } else + throw new InternalExpressionException("'weather' between 0 and 2 arguments"); + }); + expression.addLazyFunction("pos", 1, (c, t, lv) -> { Value arg = lv.get(0).evalValue(c); diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index cb6aa894..ff863165 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -499,6 +499,31 @@ public class EntityValue extends Value return Value.NULL; }); + put("absorption",(e, a)->{ + if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getAbsorptionAmount()); + return Value.NULL; + }); + + put("xp",(e, a)->{ + if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).totalExperience); + return Value.NULL; + }); + + put("xp_level", (e, a)->{ + if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).experienceLevel); + return Value.NULL; + }); + + put("xp_progress", (e, a)->{ + if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).experienceProgress); + return Value.NULL; + }); + + put("score", (e, a)->{ + if(e instanceof PlayerEntity) return new NumericValue(((PlayerEntity) e).getScore()); + return Value.NULL; + }); + put("jumping", (e, a) -> { if (e instanceof LivingEntity) { @@ -1328,8 +1353,24 @@ public class EntityValue extends Value if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().addExhaustion((int) NumericValue.asNumber(v).getLong()); }); + put("absorption", (e, v) -> { + if (e instanceof PlayerEntity) ((PlayerEntity) e).setAbsorptionAmount((float) NumericValue.asNumber(v).getLong()); + }); + + put("add_xp", (e, v) -> { + if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperience(NumericValue.asNumber(v).getInt()); + }); + + put("xp_level", (e, v) -> { + if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperienceLevels(NumericValue.asNumber(v).getInt()-((PlayerEntity) e).experienceLevel); + }); + + put("xp_score", (e, v) -> { + if (e instanceof PlayerEntity) ((PlayerEntity) e).setScore(NumericValue.asNumber(v).getInt()); + }); + put("saturation", (e, v)-> { - if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient((float)NumericValue.asNumber(v).getLong()); + if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient(NumericValue.asNumber(v).getFloat()); }); put("air", (e, v) -> e.setAir(NumericValue.asNumber(v).getInt())); diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 8a90de0a..024e8f8e 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -168,7 +168,7 @@ "ChunkTicketManager_scarpetMixin", "StatType_scarpetMixin", "PointOfInterest_scarpetMixin", - "ServerWorld_scarpetEventMixin", + "ServerWorld_scarpetMixin", "HungerManagerMixin_scarpetEventsMixin", "RecipeManager_scarpetMixin", From 68560c7c5e49aba8b06ec3fce91514bcd253f135 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Dec 2020 20:04:59 +0000 Subject: [PATCH 30/64] Merge docs for 'Adding absorption and xp API, and weather control (#540) * fixes #537 * fixes #547' --- docs/scarpet/Full.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 7e34a261..3242c0cb 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2235,6 +2235,16 @@ Causes a block to be harvested by a specified player entity. Honors player item tool if applicable. If the entity is not a valid player, no block gets destroyed. If a player is not allowed to break that block, a block doesn't get destroyed either. +### `weather()`,`weather(type)`,`weather(type, ticks)` + +If called with no args, returns `'clear'`, `'rain` or `'thunder'` based on the current weather. If thundering, will +always return `'thunder'`, if not will return `'rain'` or `'clear'` based on the current weather. + +With one arg, (either `'clear'`, `'rain` or `'thunder'`), returns the number of remaining ticks for that weather type. +NB: It can thunder without there being a thunderstorm, there has to be both rain and thunder to form a storm. + +With two args, sets the weather to `type` for `ticks` ticks. + ## Block and World querying ### `pos(block), pos(entity)` @@ -3253,6 +3263,18 @@ Number indicating remaining entity health, or `null` if not applicable. Retrieves player hunger related information. For non-players, returns `null`. +### `query(e, 'absorption')` + +Gets the absorption of the player (yellow hearts, e.g when having a golden apple.) + +### `query(e,'xp')` +### `query(e,'xp_level')` +### `query(e,'xp_progress')` +### `query(e,'score')` + +Numbers related to player's xp. `xp` is the overall xp in the bar, `xp_level` is the levels seen in the hotbar, +`xp_points` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the score displayed upon death + ### `query(e, 'air')` Number indicating remaining entity health, or `null` if not applicable. @@ -3563,6 +3585,17 @@ Will set entity on fire for `ticks` ticks. Set to 0 to extinguish. Modifies directly player raw hunger components. Has no effect on non-players +### `modify(e, 'absorption', value)` + +Sets the absorption value for the player. Each point is half a yellow heart. + +### `modify(e, 'add_xp', value)` +### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_score', value)` + +Manipulates player xp values - that's the method you probably want to use +to manipulate how much 'xp' an action should give. + ### `modify(e, 'air', ticks)` Modifies entity air @@ -4553,9 +4586,10 @@ Other value types will only be converted to tags (including NBT tags) if `force` extra treatment when loading them back from NBT, but using `force` true will always produce output / never produce an exception. -### `print(expr)` +### `print(expr)`, `print(player/player_list, expr)` Displays the result of the expression to the chat. Overrides default `scarpet` behaviour of sending everyting to stderr. +Can optionally define player or list of players to send the message to. ### `format(components, ...)`, `format(l(components, ...))` From bbde2bb173a7884492a3e6dbf48c0a206746301f Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 6 Dec 2020 16:43:07 -0400 Subject: [PATCH 31/64] simplify code for print --- .../java/carpet/script/api/Auxiliary.java | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index ec5499c0..73ce0323 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -468,43 +468,30 @@ public class Auxiliary { { if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments"); ServerCommandSource s = ((CarpetContext)c).s; + MinecraftServer server = s.getMinecraftServer(); Value res = lv.get(0).evalValue(c); - Value playersVal = Value.NULL; - if (lv.size() == 2){ - playersVal = res; + List targets = null; + if (lv.size() == 2) + { + List playerValues = (res instanceof ListValue)?((ListValue) res).getItems():Collections.singletonList(res); + List playerTargets = new ArrayList<>(); + playerValues.forEach(pv -> { + ServerPlayerEntity player = EntityValue.getPlayerByValue(server, pv); + if (player == null) throw new InternalExpressionException("Cannot target player "+pv.getString()+" in print"); + playerTargets.add(player); + }); + targets = playerTargets; res = lv.get(1).evalValue(c); } - - if(playersVal instanceof ListValue){ - - List players = ((ListValue) playersVal).getItems(); - for(Value playerVal:players){ - ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), playerVal); - if (player == null) return LazyValue.NULL; - if(res instanceof FormattedTextValue) - player.getCommandSource().sendFeedback(((FormattedTextValue) res).getText(), false); - else - Messenger.m(player, "w "+res.getString()); - } - - } else if(playersVal instanceof EntityValue){ - - ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), playersVal); - if (player == null) return LazyValue.NULL; - if(res instanceof FormattedTextValue) - player.getCommandSource().sendFeedback(((FormattedTextValue) res).getText(), false); - else - Messenger.m(player, "w "+res.getString()); - - } else { - - if (res instanceof FormattedTextValue) - s.sendFeedback(((FormattedTextValue) res).getText(), false); - else - Messenger.m(s, "w " + res.getString()); - + Text message = (res instanceof FormattedTextValue)?((FormattedTextValue) res).getText():new LiteralText(res.getString()); + if (targets == null) + { + s.sendFeedback(message, false); + } + else + { + targets.forEach(p -> p.getCommandSource().sendFeedback(message, false)); } - Value finalRes = res; return (_c, _t) -> finalRes; // pass through for variables }); From 56064a49cbd462b3ffaf5195f4405afe321a32b9 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 6 Dec 2020 19:55:01 -0400 Subject: [PATCH 32/64] fixes to xp stuff, saturation and weather --- docs/scarpet/api/Entities.md | 11 +++--- .../carpet/fakes/HungerManagerInterface.java | 1 + ...HungerManagerMixin_scarpetEventsMixin.java | 14 ++++---- .../java/carpet/script/api/WorldAccess.java | 34 +++++++++++-------- .../java/carpet/script/value/EntityValue.java | 22 ++++++++---- 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 1d053fbf..8ecb1af0 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -405,8 +405,8 @@ Gets the absorption of the player (yellow hearts, e.g when having a golden apple ### `query(e,'xp_progress')` ### `query(e,'score')` -Numbers related to player's xp. `xp` is the overall xp in the bar, `xp_level` is the levels seen in the hotbar, -`xp_points` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the score displayed upon death +Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar, +`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death ### `query(e, 'air')` @@ -724,10 +724,13 @@ Sets the absorption value for the player. Each point is half a yellow heart. ### `modify(e, 'add_xp', value)` ### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_progress', value)` ### `modify(e, 'xp_score', value)` -Manipulates player xp values - that's the method you probably want to use -to manipulate how much 'xp' an action should give. +Manipulates player xp values - `'add_xp'` the method you probably want to use +to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and +`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value, +maybe you will get a double, who knows. ### `modify(e, 'air', ticks)` diff --git a/src/main/java/carpet/fakes/HungerManagerInterface.java b/src/main/java/carpet/fakes/HungerManagerInterface.java index b4df2334..f6a2787c 100644 --- a/src/main/java/carpet/fakes/HungerManagerInterface.java +++ b/src/main/java/carpet/fakes/HungerManagerInterface.java @@ -3,4 +3,5 @@ package carpet.fakes; public interface HungerManagerInterface { float getExhaustionCM(); void setExhaustionCM(float exhaust); + void setSaturationCM(float saturn); } diff --git a/src/main/java/carpet/mixins/HungerManagerMixin_scarpetEventsMixin.java b/src/main/java/carpet/mixins/HungerManagerMixin_scarpetEventsMixin.java index 3e6fcdc2..2f841f1c 100644 --- a/src/main/java/carpet/mixins/HungerManagerMixin_scarpetEventsMixin.java +++ b/src/main/java/carpet/mixins/HungerManagerMixin_scarpetEventsMixin.java @@ -8,13 +8,11 @@ import org.spongepowered.asm.mixin.Shadow; @Mixin(HungerManager.class) public class HungerManagerMixin_scarpetEventsMixin implements HungerManagerInterface { @Shadow private float exhaustion; - public float getExhaustionCM(){ - return exhaustion; - } - + @Shadow private float foodSaturationLevel; @Override - public void setExhaustionCM(float exhaust) - { - exhaustion = exhaust; - } + public float getExhaustionCM(){ return exhaustion; } + @Override + public void setExhaustionCM(float exhaust) { exhaustion = exhaust; } + @Override + public void setSaturationCM(float saturn) { foodSaturationLevel = saturn; } } diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index f1888417..a9f8e780 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -420,16 +420,17 @@ public class WorldAccess { ServerWorld world = ((CarpetContext) c).s.getWorld(); if(lv.size()==0)//cos it can thunder when raining or when clear. - return (_c,_t)->new StringValue( world.isThundering()? "thunder" : (world.isRaining()? "rain":"clear")); - + { + Value ret = new StringValue(world.isThundering() ? "thunder" : (world.isRaining() ? "rain" : "clear")); + return (_c, _t) -> ret; + } Value weather = lv.get(0).evalValue(c); ServerWorldProperties worldProperties = ((ServerWorldInterface) world).getWorldPropertiesCM(); - - if(lv.size()==1) { + if(lv.size()==1) + { int ticks; - - switch (weather.getString()) { + switch (weather.getString().toLowerCase(Locale.ROOT)) { case "clear": ticks = worldProperties.getClearWeatherTime(); break; @@ -442,11 +443,14 @@ public class WorldAccess { default: throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); } - return (_c,_t) -> new NumericValue(ticks); - } else if(lv.size()==2){ - int ticks = ((NumericValue)lv.get(1).evalValue(c)).getInt(); - - switch (weather.getString()){ + Value ret = new NumericValue(ticks); + return (_c,_t) -> ret; + } + if(lv.size()==2) + { + int ticks = NumericValue.asNumber(lv.get(1).evalValue(c), "tick_time in 'weather'").getInt(); + switch (weather.getString().toLowerCase(Locale.ROOT)) + { case "clear": world.setWeather(ticks,0,false,false); break; @@ -467,10 +471,10 @@ public class WorldAccess { default: throw new InternalExpressionException("Weather can only be 'clear', 'rain' or 'thunder'"); } - - return LazyValue.TRUE; - } else - throw new InternalExpressionException("'weather' between 0 and 2 arguments"); + Value ret = NumericValue.of(ticks); + return (_c, _t) -> ret; + } + throw new InternalExpressionException("'weather' requires 0, 1 or 2 arguments"); }); expression.addLazyFunction("pos", 1, (c, t, lv) -> diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index ff863165..c30ff0aa 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -30,6 +30,7 @@ import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.network.packet.s2c.play.EntityPositionS2CPacket; +import net.minecraft.network.packet.s2c.play.ExperienceBarUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket; import net.minecraft.command.EntitySelector; import net.minecraft.command.EntitySelectorReader; @@ -1354,26 +1355,35 @@ public class EntityValue extends Value }); put("absorption", (e, v) -> { - if (e instanceof PlayerEntity) ((PlayerEntity) e).setAbsorptionAmount((float) NumericValue.asNumber(v).getLong()); + if (e instanceof PlayerEntity) ((PlayerEntity) e).setAbsorptionAmount((float) NumericValue.asNumber(v, "absorbtion").getLong()); }); put("add_xp", (e, v) -> { - if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperience(NumericValue.asNumber(v).getInt()); + if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperience(NumericValue.asNumber(v, "add_xp").getInt()); }); put("xp_level", (e, v) -> { - if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperienceLevels(NumericValue.asNumber(v).getInt()-((PlayerEntity) e).experienceLevel); + if (e instanceof PlayerEntity) ((PlayerEntity) e).addExperienceLevels(NumericValue.asNumber(v, "xp_level").getInt()-((PlayerEntity) e).experienceLevel); + }); + + put("xp_progress", (e, v) -> { + if (e instanceof ServerPlayerEntity) + { + ServerPlayerEntity p = (ServerPlayerEntity) e; + p.experienceProgress = NumericValue.asNumber(v, "xp_progress").getFloat(); + p.networkHandler.sendPacket(new ExperienceBarUpdateS2CPacket(p.experienceProgress, p.totalExperience, p.experienceLevel)); + } }); put("xp_score", (e, v) -> { - if (e instanceof PlayerEntity) ((PlayerEntity) e).setScore(NumericValue.asNumber(v).getInt()); + if (e instanceof PlayerEntity) ((PlayerEntity) e).setScore(NumericValue.asNumber(v, "xp_score").getInt()); }); put("saturation", (e, v)-> { - if(e instanceof PlayerEntity) ((PlayerEntity) e).getHungerManager().setSaturationLevelClient(NumericValue.asNumber(v).getFloat()); + if(e instanceof PlayerEntity) ((HungerManagerInterface) ((PlayerEntity) e).getHungerManager()).setSaturationCM(NumericValue.asNumber(v, "saturation").getFloat()); }); - put("air", (e, v) -> e.setAir(NumericValue.asNumber(v).getInt())); + put("air", (e, v) -> e.setAir(NumericValue.asNumber(v, "air").getInt())); put("breaking_progress", (e, a) -> { if (e instanceof ServerPlayerEntity) From b318f1fb68330371dced63d26777f76030f18b1a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 6 Dec 2020 23:55:24 +0000 Subject: [PATCH 33/64] Merge docs for 'fixes to xp stuff, saturation and weather' --- docs/scarpet/Full.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 3242c0cb..2c44c590 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -3272,8 +3272,8 @@ Gets the absorption of the player (yellow hearts, e.g when having a golden apple ### `query(e,'xp_progress')` ### `query(e,'score')` -Numbers related to player's xp. `xp` is the overall xp in the bar, `xp_level` is the levels seen in the hotbar, -`xp_points` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the score displayed upon death +Numbers related to player's xp. `xp` is the overall xp player has, `xp_level` is the levels seen in the hotbar, +`xp_progress` is a float between 0 and 1 indicating the percentage of the xp bar filled, and `score` is the number displayed upon death ### `query(e, 'air')` @@ -3591,10 +3591,13 @@ Sets the absorption value for the player. Each point is half a yellow heart. ### `modify(e, 'add_xp', value)` ### `modify(e, 'xp_level', value)` +### `modify(e, 'xp_progress', value)` ### `modify(e, 'xp_score', value)` -Manipulates player xp values - that's the method you probably want to use -to manipulate how much 'xp' an action should give. +Manipulates player xp values - `'add_xp'` the method you probably want to use +to manipulate how much 'xp' an action should give. `'xp_score'` only affects the number you see when you die, and +`'xp_progress'` controls the xp progressbar above the hotbar, should take values from 0 to 1, but you can set it to any value, +maybe you will get a double, who knows. ### `modify(e, 'air', ticks)` From 49e5c012b3ec6199f34a54f4652c0a35943bf8d6 Mon Sep 17 00:00:00 2001 From: Ghoulboy Date: Mon, 7 Dec 2020 01:32:29 +0100 Subject: [PATCH 34/64] Adding Gamerule modification to scarpet (#610) --- docs/scarpet/api/Auxiliary.md | 6 ++++-- src/main/java/carpet/script/api/Auxiliary.java | 3 --- src/main/java/carpet/script/utils/SystemInfo.java | 12 ++++++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 10544321..06fa8784 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -487,7 +487,9 @@ Available options in the scarpet app space: * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. - + * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`. + + Relevant gameplay related properties * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'` * `game_hardcore` - boolean whether the game is in hardcore mode @@ -517,4 +519,4 @@ Available options in the scarpet app space: * `java_process_cpu_load` - current percentage of CPU used by JVM Scarpet related properties - * `scarpet_version` - returns the version of the carpet your scarpet comes with. \ No newline at end of file + * `scarpet_version` - returns the version of the carpet your scarpet comes with. diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 73ce0323..367fe2d1 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -35,7 +35,6 @@ import carpet.script.value.ValueConversions; import carpet.utils.Messenger; import com.google.common.collect.ImmutableMap; import net.minecraft.block.BlockState; -import net.minecraft.command.argument.EntityAnchorArgumentType; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; @@ -50,7 +49,6 @@ import net.minecraft.network.packet.s2c.play.TitleS2CPacket; import net.minecraft.network.packet.s2c.play.TitleS2CPacket.Action; import net.minecraft.particle.ParticleEffect; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.command.CommandOutput; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; @@ -64,7 +62,6 @@ import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.EulerAngle; -import net.minecraft.util.math.Vec2f; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.DynamicRegistryManager; import net.minecraft.util.registry.Registry; diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index 86b13735..e5984e7b 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -13,6 +13,7 @@ import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; import net.minecraft.util.WorldSavePath; +import net.minecraft.world.GameRules; import java.lang.management.ManagementFactory; import java.nio.file.Path; @@ -124,6 +125,17 @@ public class SystemInfo { }); return carpetRules; }); + put("world_gamerules", c->{ + Map rules = new HashMap<>(); + final GameRules gameRules = c.s.getWorld().getGameRules(); + GameRules.accept(new GameRules.Visitor() { + @Override + public > void visit(GameRules.Key key, GameRules.Type type) { + rules.put(StringValue.of(key.getName()), StringValue.of(gameRules.get(key).toString())); + } + }); + return MapValue.wrap(rules); + }); put("scarpet_version", c -> StringValue.of(CarpetSettings.carpetVersion)); }}; From f28753276526c8624c5b9fdac173d90d805a83e6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Dec 2020 00:32:56 +0000 Subject: [PATCH 35/64] Merge docs for 'Adding Gamerule modification to scarpet (#610)' --- docs/scarpet/Full.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 2c44c590..c21745b7 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -4883,7 +4883,9 @@ Available options in the scarpet app space: * `world_path` - full path to the world saves folder * `world_folder` - name of the direct folder in the saves that holds world files * `world_carpet_rules` - returns all Carpet rules in a map form (`rule`->`value`). Note that the values are always returned as strings, so you can't do boolean comparisons directly. Includes rules from extensions with their namespace (`namespace:rule`->`value`). You can later listen to rule changes with the `on_carpet_rule_changes(rule, newValue)` event. - + * `world_gamerules` - returns all gamerules in a map form (`rule`->`value`). Like carpet rules, values are returned as strings, so you can use appropriate value conversions using `bool()` or `number()` to convert them to other values. Gamerules are read-only to discourage app programmers to mess up with the settings intentionally applied by server admins. Isn't that just super annoying when a datapack messes up with your gamerule settings? It is still possible to change them though using `run('gamerule ...`. + + Relevant gameplay related properties * `game_difficulty` - current difficulty of the game: `'peacefu'`, `'easy'`, `'normal'`, or `'hard'` * `game_hardcore` - boolean whether the game is in hardcore mode @@ -4913,7 +4915,8 @@ Available options in the scarpet app space: * `java_process_cpu_load` - current percentage of CPU used by JVM Scarpet related properties - * `scarpet_version` - returns the version of the carpet your scarpet comes with.# `/script run` command + * `scarpet_version` - returns the version of the carpet your scarpet comes with. +# `/script run` command Primary way to input commands. The command executes in the context, position, and dimension of the executing player, commandblock, etc... The command receives 4 variables, `x`, `y`, `z` and `p` indicating position and From ac6b3c7f483df29c62bcf2de175bd478a7fb0f04 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 6 Dec 2020 23:40:02 -0400 Subject: [PATCH 36/64] apps can be organized in floders in your shared instance space. --- docs/scarpet/api/Overview.md | 3 ++- src/main/java/carpet/script/CarpetScriptServer.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 7c08b451..4f1da73c 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. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world. +folder, flat. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world, +and here you can actually organize them in folders. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the `__config()` function, if present, which will be executed once. Loading the app will also bind specific diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index d7090999..b515489e 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -149,7 +149,7 @@ public class CarpetScriptServer { Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); Files.createDirectories(globalFolder); - scriptPath = Files.list(globalFolder) + scriptPath = Files.walk(globalFolder) .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") || (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl"))) .findFirst(); @@ -194,7 +194,7 @@ public class CarpetScriptServer try { Path worldScripts = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); Files.createDirectories(worldScripts); - Files.walk(worldScripts) + Files.list(worldScripts) .filter(f -> f.toString().endsWith(".sc")) .forEach(f -> moduleNames.add(f.getFileName().toString().replaceFirst("\\.sc$","").toLowerCase(Locale.ROOT))); From a0067d0d6122dcd5394eba1e76f1119773fd45b6 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 7 Dec 2020 00:12:55 -0400 Subject: [PATCH 37/64] added item_list, item_tags to support item tags as well as options for entities to support entity type tags. fixes #382 --- docs/scarpet/api/Entities.md | 20 +++++++--- docs/scarpet/api/Inventories.md | 8 ++++ docs/scarpet/resources/editors/idea/2.txt | 2 + .../scarpet/resources/editors/npp/scarpet.xml | 2 +- src/main/java/carpet/script/api/Entities.java | 25 +++++++----- .../java/carpet/script/api/Inventories.java | 40 +++++++++++++++++++ .../java/carpet/script/value/EntityValue.java | 35 +++++++++++++++- .../script/value/NBTSerializableValue.java | 2 +- 8 files changed, 116 insertions(+), 18 deletions(-) diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 8ecb1af0..0ebc7a59 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -52,7 +52,9 @@ to the `regular` group. * `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that category. -* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: +* All entity tags including those provided with datapacks. Built-in entity tags include: `skeletons`, `raiders`, +`beehive_inhabitors` (bee, duh), `arrows` and `impact_projectiles`. +* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: `area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`, `chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`, `egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`, @@ -209,13 +211,21 @@ List of entities riding the entity. Entity that `e` rides. -### `query(e, 'tags')` +### `query(e, 'scoreboard_tags')`, `query(e, 'tags')`(deprecated) -List of entity's tags. +List of entity's scoreboard tags. -### `query(e, 'has_tag',tag)` +### `query(e, 'has_scoreboard_tag',tag)`, `query(e, 'has_tag',tag)`(deprecated) -Boolean, true if the entity is marked with `tag`. +Boolean, true if the entity is marked with a `tag` scoreboad tag. + +### `query(e, 'entity_tags')` + +List of entity tags assigned to the type this entity represents. + +### `query(e, 'has_entity_tag', tag)` + +Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid. ### `query(e, 'is_burning')` diff --git a/docs/scarpet/api/Inventories.md b/docs/scarpet/api/Inventories.md index 1ecc692b..ebe2ed7b 100644 --- a/docs/scarpet/api/Inventories.md +++ b/docs/scarpet/api/Inventories.md @@ -28,6 +28,14 @@ an inventory, all API functions typically do nothing and return null. Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item. +### item_list(tag?) + +With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid. + +### item_tags(item, tag?) + +Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag + ### `stack_limit(item)` Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index e7f1f8f6..397c1760 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -52,6 +52,8 @@ inventory_remove inventory_set inventory_size item_category +item_list +item_tags last_tick_times light liquid diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 46f3a139..759e69d0 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -68,7 +68,7 @@ player spawn entity_id entity_list entity_area entity_selector query modify entity_event entity_load_handler entity_types - stack_limit item_category recipe_data crafting_remaining_item + item_list item_tags stack_limit item_category recipe_data crafting_remaining_item inventory_size inventory_has_items inventory_get inventory_set inventory_find inventory_remove drop_item diff --git a/src/main/java/carpet/script/api/Entities.java b/src/main/java/carpet/script/api/Entities.java index c5a137a9..ccf35938 100644 --- a/src/main/java/carpet/script/api/Entities.java +++ b/src/main/java/carpet/script/api/Entities.java @@ -23,6 +23,7 @@ import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundTag; import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; @@ -34,6 +35,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -183,8 +185,9 @@ public class Entities { expression.addLazyFunction("entity_list", 1, (c, t, lv) -> { String who = lv.get(0).evalValue(c).getString(); - EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who); - List entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, eDesc.filteringPredicate); + ServerCommandSource source = ((CarpetContext)c).s; + EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who, source.getMinecraftServer()); + List entityList = source.getWorld().getEntitiesByType(eDesc.directType, eDesc.filteringPredicate); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); return (_c, _t ) -> retval; }); @@ -212,8 +215,8 @@ public class Entities { throw new InternalExpressionException("Range of 'entity_area' cannot come from a block argument"); Vec3d range = rangeLocator.vec; Box area = centerBox.expand(range.x, range.y, range.z); - EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who); - List entityList = ((CarpetContext)c).s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate); + EntityValue.EntityClassDescriptor eDesc = EntityValue.getEntityDescriptor(who, cc.s.getMinecraftServer()); + List entityList = cc.s.getWorld().getEntitiesByType(eDesc.directType, area,eDesc.filteringPredicate); Value retval = ListValue.wrap(entityList.stream().map(EntityValue::new).collect(Collectors.toList())); return (_c, _t ) -> retval; }); @@ -238,7 +241,9 @@ public class Entities { Value v = lv.get(0).evalValue(c); if (!(v instanceof EntityValue)) throw new InternalExpressionException("First argument to query should be an entity"); - String what = lv.get(1).evalValue(c).getString(); + String what = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); + if (what.equals("tags")) + c.host.issueDeprecation("'tags' for entity querying"); Value retval; if (lv.size()==2) retval = ((EntityValue) v).get(what, null); @@ -269,11 +274,13 @@ public class Entities { return (cc, tt) -> v; }); - expression.addFunction("entity_types", (lv) -> + expression.addLazyFunction("entity_types", -1, (c, t, lv) -> { + if (lv.size() > 1) throw new InternalExpressionException("'entity_types' requires one or no arguments"); - String desc = (lv.size() == 1)?lv.get(0).getString():"*"; - return EntityValue.getEntityDescriptor(desc).listValue; + String desc = (lv.size() == 1)?lv.get(0).evalValue(c).getString():"*"; + Value ret = EntityValue.getEntityDescriptor(desc, ((CarpetContext) c).s.getMinecraftServer()).listValue; + return (_c, _t) -> ret; }); expression.addLazyFunction("entity_load_handler", -1, (c, t, lv) -> @@ -285,7 +292,7 @@ public class Entities { ? ((ListValue) entityValue).getItems().stream().map(Value::getString).collect(Collectors.toList()) : Collections.singletonList(entityValue.getString()); Set> types = new HashSet<>(); - descriptors.forEach(s -> types.addAll(EntityValue.getEntityDescriptor(s).typeList)); + descriptors.forEach(s -> types.addAll(EntityValue.getEntityDescriptor(s, ((CarpetContext) c).s.getMinecraftServer()).typeList)); FunctionArgument funArg = FunctionArgument.findIn(c, expression.module, lv, 1, true, false); CarpetEventServer events = ((CarpetScriptHost)c.host).getScriptServer().events; if (funArg.function == null) diff --git a/src/main/java/carpet/script/api/Inventories.java b/src/main/java/carpet/script/api/Inventories.java index b5f6bfb4..fb4983ae 100644 --- a/src/main/java/carpet/script/api/Inventories.java +++ b/src/main/java/carpet/script/api/Inventories.java @@ -12,6 +12,7 @@ import carpet.script.value.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; +import carpet.script.value.ValueConversions; import com.google.common.collect.Sets; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.argument.ItemStackArgument; @@ -32,6 +33,7 @@ import net.minecraft.recipe.ShapedRecipe; import net.minecraft.recipe.ShapelessRecipe; import net.minecraft.recipe.SpecialCraftingRecipe; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.tag.TagManager; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; import net.minecraft.util.math.Vec3d; @@ -40,6 +42,7 @@ import net.minecraft.util.registry.Registry; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class Inventories { public static void apply(Expression expression) @@ -60,6 +63,43 @@ public class Inventories { return (_c, _t) -> res; }); + expression.addLazyFunction("item_list", -1, (c, t, lv) -> + { + if (lv.size() == 0) + { + Value res = ListValue.wrap(Registry.ITEM.getIds().stream().map(ValueConversions::of).collect(Collectors.toList())); + return (cc, tt) -> res; + } + CarpetContext cc = (CarpetContext)c; + TagManager tagManager = cc.s.getMinecraftServer().getTagManager(); + String tag = lv.get(0).evalValue(c).getString(); + net.minecraft.tag.Tag itemTag = tagManager.getItems().getTag(new Identifier(tag)); + if (itemTag == null) return LazyValue.NULL; + Value ret = ListValue.wrap(itemTag.values().stream().map(b -> ValueConversions.of(Registry.ITEM.getId(b))).collect(Collectors.toList())); + return (_c, _t) -> ret; + }); + + expression.addLazyFunction("item_tags", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + TagManager tagManager = cc.s.getMinecraftServer().getTagManager(); + if (lv.size() == 0) + { + Value ret = ListValue.wrap(tagManager.getItems().getTagIds().stream().map(ValueConversions::of).collect(Collectors.toList())); + return (_c, _t) -> ret; + } + Item item = NBTSerializableValue.parseItem(lv.get(0).evalValue(c).getString()).getItem(); + if (lv.size() == 1) + { + Value ret = ListValue.wrap(tagManager.getItems().getTagsFor(item).stream().map(ValueConversions::of).collect(Collectors.toList())); + return (_c, _t) -> ret; + } + String tag = lv.get(1).evalValue(c).getString(); + net.minecraft.tag.Tag itemTag = tagManager.getItems().getTag(new Identifier(tag)); + if (itemTag == null) return LazyValue.NULL; + return item.isIn(itemTag)?LazyValue.TRUE:LazyValue.FALSE; + }); + expression.addLazyFunction("recipe_data", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index c30ff0aa..89535a7c 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -223,10 +223,28 @@ public class EntityValue extends Value return entity.hashCode(); } - public static EntityClassDescriptor getEntityDescriptor(String who) + public static EntityClassDescriptor getEntityDescriptor(String who, MinecraftServer server) { EntityClassDescriptor eDesc = EntityClassDescriptor.byName.get(who); - if (eDesc == null) throw new InternalExpressionException(who+" is not a valid entity descriptor"); + if (eDesc == null) + { + boolean positive = true; + if (who.startsWith("!")) + { + positive = false; + who = who.substring(1); + } + net.minecraft.tag.Tag> eTag = server.getTagManager().getEntityTypes().getTag(new Identifier(who)); + if (eTag == null) throw new InternalExpressionException(who+" is not a valid entity descriptor"); + if (positive) + { + return new EntityClassDescriptor(null, e -> eTag.contains(e.getType()) && e.isAlive(), eTag.values().stream()); + } + else + { + return new EntityClassDescriptor(null, e -> !eTag.contains(e.getType()) && e.isAlive(), Registry.ENTITY_TYPE.stream().filter(et -> !eTag.contains(et))); + } + } return eDesc; //TODO add more here like search by tags, or type //if (who.startsWith('tag:')) @@ -401,8 +419,21 @@ public class EntityValue extends Value put("passengers", (e, a) -> ListValue.wrap(e.getPassengerList().stream().map(EntityValue::new).collect(Collectors.toList()))); put("mount", (e, a) -> (e.getVehicle()!=null)?new EntityValue(e.getVehicle()):Value.NULL); put("unmountable", (e, a) -> new NumericValue(((EntityInterface)e).isPermanentVehicle())); + // deprecated put("tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); + + put("scoreboard_tags", (e, a) -> ListValue.wrap(e.getScoreboardTags().stream().map(StringValue::new).collect(Collectors.toList()))); + put("entity_tags", (e, a) -> ListValue.wrap(e.getServer().getTagManager().getEntityTypes().getTagsFor(e.getType()).stream().map(ValueConversions::of).collect(Collectors.toList()))); + // deprecated put("has_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); + + put("has_scoreboard_tag", (e, a) -> new NumericValue(e.getScoreboardTags().contains(a.getString()))); + put("has_entity_tag", (e, a) -> { + net.minecraft.tag.Tag> tag = e.getServer().getTagManager().getEntityTypes().getTag(new Identifier(a.getString())); + if (tag == null) return Value.NULL; + return new NumericValue(e.getType().isIn(tag)); + }); + put("yaw", (e, a)-> new NumericValue(e.yaw)); put("head_yaw", (e, a)-> { if (e instanceof LivingEntity) diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 938acd20..877300dc 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -267,7 +267,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa } } - private static Map itemCache = new HashMap<>(); + private static final Map itemCache = new HashMap<>(); public static ItemStackArgument parseItem(String itemString) { From f400c9c0063b32a83486f21411d08dd53320f278 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Dec 2020 04:16:59 +0000 Subject: [PATCH 38/64] Merge docs for 'added item_list, item_tags to support item tags as well as options for entities to support entity type tags. fixes #382' --- docs/scarpet/Full.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index c21745b7..e8c7e07f 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1757,7 +1757,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. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world. +folder, flat. In singleplayer, you can also save apps in `.minecraft/config/carpet/scripts` for them to be available in any world, +and here you can actually organize them in folders. When loaded (via `/script load` command, etc.), the game will run the content of the app once, regardless of its scope (more about the app scopes below), without executing of any functions, unless called directly, and with the exception of the `__config()` function, if present, which will be executed once. Loading the app will also bind specific @@ -2919,7 +2920,9 @@ to the `regular` group. * `monster`, `creature`, `ambient`, `water_creature`, `water_ambient`, `misc` - another categorization of living entities based on their spawn group. Negative descriptor resolves to all living types that don't belong to that category. -* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: +* All entity tags including those provided with datapacks. Built-in entity tags include: `skeletons`, `raiders`, +`beehive_inhabitors` (bee, duh), `arrows` and `impact_projectiles`. +* Any of the following standard entity types (equivalent to selection from `/summon` vanilla command: `area_effect_cloud`, `armor_stand`, `arrow`, `bat`, `bee`, `blaze`, `boat`, `cat`, `cave_spider`, `chest_minecart`, `chicken`, `cod`, `command_block_minecart`, `cow`, `creeper`, `dolphin`, `donkey`, `dragon_fireball`, `drowned`, `egg`, `elder_guardian`, `end_crystal`, `ender_dragon`, `ender_pearl`, `enderman`, `endermite`, `evoker`, @@ -3076,13 +3079,21 @@ List of entities riding the entity. Entity that `e` rides. -### `query(e, 'tags')` +### `query(e, 'scoreboard_tags')`, `query(e, 'tags')`(deprecated) -List of entity's tags. +List of entity's scoreboard tags. -### `query(e, 'has_tag',tag)` +### `query(e, 'has_scoreboard_tag',tag)`, `query(e, 'has_tag',tag)`(deprecated) -Boolean, true if the entity is marked with `tag`. +Boolean, true if the entity is marked with a `tag` scoreboad tag. + +### `query(e, 'entity_tags')` + +List of entity tags assigned to the type this entity represents. + +### `query(e, 'has_entity_tag', tag)` + +Returns `true` if the entity matches that entity tag, `false` if it doesn't, and `null` if the tag is not valid. ### `query(e, 'is_burning')` @@ -3744,6 +3755,14 @@ an inventory, all API functions typically do nothing and return null. Most items returned are in the form of a triple of item name, count, and nbt or the extra data associated with an item. +### item_list(tag?) + +With no arguments, returns a list of all items in the game. With an item tag provided, list items matching the tag, or `null` if tag is not valid. + +### item_tags(item, tag?) + +Returns list of tags the item belongs to, or, if tag is provided, `true` if an item maches the tag, `false` if it doesn't and `null` if that's not a valid tag + ### `stack_limit(item)` Returns number indicating what is the stack limit for the item. Its typically 1 (non-stackable), 16 (like buckets), From e8a6b8936fb6a085411462da28cbbabf1e1f0b5b Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 7 Dec 2020 00:32:08 -0400 Subject: [PATCH 39/64] as per discussion, since scripts can now be stowed in the global bin, it makes sense for apps to behave like datapacks. fixes #613 --- src/main/java/carpet/CarpetSettings.java | 2 +- src/main/java/carpet/script/CarpetScriptHost.java | 2 +- src/main/java/carpet/script/CarpetScriptServer.java | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index fbe1ac98..78257a10 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -427,7 +427,7 @@ public class CarpetSettings extra = "if /script is enabled", category = SCARPET ) - public static boolean scriptsAutoload = false; + public static boolean scriptsAutoload = true; @Rule(desc = "Enables /player command to control/spawn players", category = COMMAND) public static String commandPlayer = "ops"; diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 4a5a02ef..12eff864 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -217,7 +217,7 @@ public class CarpetScriptHost extends ScriptHost if (!(ret instanceof MapValue)) return false; Map config = ((MapValue) ret).getMap(); setPerPlayer(config.getOrDefault(new StringValue("scope"), new StringValue("player")).getString().equalsIgnoreCase("player")); - persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.FALSE).getBoolean(); + persistenceRequired = config.getOrDefault(new StringValue("stay_loaded"), Value.TRUE).getBoolean(); // read custom arguments Value arguments = config.get(StringValue.of("arguments")); if (arguments != null) diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index b515489e..736ed338 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -122,7 +122,6 @@ public class CarpetScriptServer }); if (CarpetSettings.scriptsAutoload) { - Messenger.m(server.getCommandSource(), "Auto-loading world scarpet apps"); for (String moduleName: listAvailableModules(false)) { addScriptHost(server.getCommandSource(), moduleName, null, true, true, false); From 522d97168856ed85a0597db8f60cbc390a4b97c9 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 7 Dec 2020 01:11:55 -0400 Subject: [PATCH 40/64] added startup and shutdown events. fixes #586 --- docs/scarpet/api/Events.md | 7 ++++++ src/main/java/carpet/CarpetServer.java | 4 ++-- .../java/carpet/script/CarpetEventServer.java | 24 +++++++++++++++++++ .../carpet/script/CarpetScriptServer.java | 3 ++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index b337090d..18e3f2ba 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -32,6 +32,13 @@ if it accepts required number of parameters. Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance is responsible independently from handling their events, so a global event may be executed multiple times for each player. +### `__on_server_starts()` +Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`. + +### `__on_server_shuts_down()` +Event triggers when the server started the shutdown process, before `__on_close()` is executed. Unlike `__on_close()`, it doesn't +trigger with `/reload`. + ### `__on_tick()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` to access other dimensions from there. diff --git a/src/main/java/carpet/CarpetServer.java b/src/main/java/carpet/CarpetServer.java index d069fe81..773b61e3 100644 --- a/src/main/java/carpet/CarpetServer.java +++ b/src/main/java/carpet/CarpetServer.java @@ -102,9 +102,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit public static void onServerLoadedWorlds(MinecraftServer minecraftServer) { + HopperCounter.resetAll(minecraftServer); extensions.forEach(e -> e.onServerLoadedWorlds(minecraftServer)); scriptServer.initializeForWorld(); - HopperCounter.resetAll(minecraftServer); } public static void tick(MinecraftServer server) @@ -169,9 +169,9 @@ public class CarpetServer implements ClientModInitializer,DedicatedServerModInit public static void onServerClosed(MinecraftServer server) { + if (scriptServer != null) scriptServer.onClose(); ServerNetworkHandler.close(); currentCommandDispatcher = null; - if (scriptServer != null) scriptServer.onClose(); LoggerRegistry.stopLoggers(); extensions.forEach(e -> e.onServerClosed(server)); diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 04599e8d..11e89c03 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -309,6 +309,30 @@ public class CarpetEventServer return events; } + public static final Event START = new Event("server_starts", 0, true) + { + @Override + public void onTick() + { + handler.call(Collections::emptyList, () -> + CarpetServer.minecraft_server.getCommandSource(). + withWorld(CarpetServer.minecraft_server.getWorld(World.OVERWORLD)) + ); + } + }; + + public static final Event SHUTDOWN = new Event("server_shuts_down", 0, true) + { + @Override + public void onTick() + { + handler.call(Collections::emptyList, () -> + CarpetServer.minecraft_server.getCommandSource(). + withWorld(CarpetServer.minecraft_server.getWorld(World.OVERWORLD)) + ); + } + }; + public static final Event TICK = new Event("tick", 0, true) { @Override diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 736ed338..41b2fc34 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -127,7 +127,7 @@ public class CarpetScriptServer addScriptHost(server.getCommandSource(), moduleName, null, true, true, false); } } - + CarpetEventServer.Event.START.onTick(); } public Module getModule(String name, boolean allowLibraries) @@ -489,6 +489,7 @@ public class CarpetScriptServer public void onClose() { + CarpetEventServer.Event.SHUTDOWN.onTick(); for (ScriptHost host : modules.values()) { host.onClose(); From fb2602eea8e5bba8de69d80c362b885ec5ab19f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Dec 2020 05:12:40 +0000 Subject: [PATCH 41/64] Merge docs for 'added startup and shutdown events. fixes #586' --- docs/scarpet/Full.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index e8c7e07f..78b53a0d 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -3959,6 +3959,13 @@ if it accepts required number of parameters. Global events will be handled once per app that is with `'global'` scope. With `player` scoped apps, each player instance is responsible independently from handling their events, so a global event may be executed multiple times for each player. +### `__on_server_starts()` +Event triggers after world is loaded and after all startup apps have started. It won't be triggered with `/reload`. + +### `__on_server_shuts_down()` +Event triggers when the server started the shutdown process, before `__on_close()` is executed. Unlike `__on_close()`, it doesn't +trigger with `/reload`. + ### `__on_tick()` Event triggers at the beginning of each tick, located in the overworld. You can use `in_dimension()` to access other dimensions from there. From 990a377f0befd14ec57bcb309e9f433cf580b5f1 Mon Sep 17 00:00:00 2001 From: Ghoulboy Date: Mon, 7 Dec 2020 13:38:02 +0100 Subject: [PATCH 42/64] Adding convert_date and unix_time to docs (#614) --- docs/scarpet/resources/editors/idea/2.txt | 4 +++- docs/scarpet/resources/editors/npp/scarpet.xml | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/scarpet/resources/editors/idea/2.txt b/docs/scarpet/resources/editors/idea/2.txt index 397c1760..25f4e954 100644 --- a/docs/scarpet/resources/editors/idea/2.txt +++ b/docs/scarpet/resources/editors/idea/2.txt @@ -15,6 +15,7 @@ blocks_movement bossbar brightness chunk_tickets +convert_date crafting_remaining_item create_marker current_dimension @@ -125,8 +126,9 @@ ticks_randomly top transparent update +unix_time view_distance volume without_updates world_time -write_file \ No newline at end of file +write_file diff --git a/docs/scarpet/resources/editors/npp/scarpet.xml b/docs/scarpet/resources/editors/npp/scarpet.xml index 759e69d0..81706a3d 100644 --- a/docs/scarpet/resources/editors/npp/scarpet.xml +++ b/docs/scarpet/resources/editors/npp/scarpet.xml @@ -58,6 +58,8 @@ structure_eligibility structures structure_references set_structure reset_chunk reload_chunk custom_dimension add_chunk_ticket + convert_date unix_time + display_title scoreboard scoreboard_add scoreboard_remove scoreboard_display From bf0b75ebff7d878415db99ecbb40c0dd95293913 Mon Sep 17 00:00:00 2001 From: Ghoulboy Date: Mon, 7 Dec 2020 13:39:04 +0100 Subject: [PATCH 43/64] adding dev_environment (#615) --- docs/scarpet/api/Auxiliary.md | 1 + src/main/java/carpet/script/utils/SystemInfo.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/scarpet/api/Auxiliary.md b/docs/scarpet/api/Auxiliary.md index 06fa8784..49a24255 100644 --- a/docs/scarpet/api/Auxiliary.md +++ b/docs/scarpet/api/Auxiliary.md @@ -507,6 +507,7 @@ Available options in the scarpet app space: * `server_whitelist` - list of players allowed to log in * `server_banned_players` - list of banned player names * `server_banned_ips` - list of banned IP addresses + * `server_dev_environment` - boolean indicating whether this server is in a development environment. System related properties * `java_max_memory` - maximum allowed memory accessible by JVM diff --git a/src/main/java/carpet/script/utils/SystemInfo.java b/src/main/java/carpet/script/utils/SystemInfo.java index e5984e7b..8d1ac7c2 100644 --- a/src/main/java/carpet/script/utils/SystemInfo.java +++ b/src/main/java/carpet/script/utils/SystemInfo.java @@ -12,6 +12,7 @@ import carpet.script.value.Value; import carpet.settings.ParsedRule; import carpet.settings.SettingsManager; import com.sun.management.OperatingSystemMXBean; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.util.WorldSavePath; import net.minecraft.world.GameRules; @@ -82,6 +83,7 @@ public class SystemInfo { } return whitelist; }); + put("server_dev_environment", c-> new NumericValue(FabricLoader.getInstance().isDevelopmentEnvironment())); put("java_max_memory", c -> new NumericValue(Runtime.getRuntime().maxMemory())); put("java_allocated_memory", c -> new NumericValue(Runtime.getRuntime().totalMemory())); @@ -117,7 +119,7 @@ public class SystemInfo { CarpetServer.extensions.forEach(e -> { SettingsManager manager = e.customSettingsManager(); if (manager == null) return; - + Collection> extensionRules = manager.getRules(); extensionRules.forEach(rule -> { carpetRules.put(new StringValue(manager.getIdentifier()+":"+rule.name), new StringValue(rule.getAsString())); From c7554c888e065c9b1c4e806e5434c495c2131ae8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Dec 2020 12:39:25 +0000 Subject: [PATCH 44/64] Merge docs for 'adding dev_environment (#615)' --- docs/scarpet/Full.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 78b53a0d..5b25bd4e 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -4929,6 +4929,7 @@ Available options in the scarpet app space: * `server_whitelist` - list of players allowed to log in * `server_banned_players` - list of banned player names * `server_banned_ips` - list of banned IP addresses + * `server_dev_environment` - boolean indicating whether this server is in a development environment. System related properties * `java_max_memory` - maximum allowed memory accessible by JVM From f54a0989483eb9119bd206031b345a252780fd16 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 7 Dec 2020 16:03:42 -0400 Subject: [PATCH 45/64] allow apps define their own command permission level, fixes #542 --- docs/scarpet/api/Overview.md | 20 ++++++-- .../java/carpet/script/CarpetScriptHost.java | 48 ++++++++++++++++++- .../carpet/script/CarpetScriptServer.java | 14 +++++- 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/docs/scarpet/api/Overview.md b/docs/scarpet/api/Overview.md index 4f1da73c..7ec68245 100644 --- a/docs/scarpet/api/Overview.md +++ b/docs/scarpet/api/Overview.md @@ -38,10 +38,9 @@ To access global/server state for a player app, which you shouldn't do, you need so either use a command block, or any arbitrary entity: `/execute as @e[type=bat,limit=1] run script in globals` for instance, however running anything in the global scope for a `'player'` scoped app is not intended. -* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will +* `'stay_loaded'`: defaults to `true`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. -This is to allow to store multiple apps on the server/world and selectively decide which one should be running at -startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called + WARNING: all apps will run once at startup anyways, so be aware that their actions that are called statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded. * `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function, all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type @@ -52,9 +51,20 @@ conflicts and ambiguities between different paths of execution. While ambiguous and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and `true`, then scarpet will load all provided commands regardless. +* `'command_permission'` - indicates a custom permission to run the command. It can either be a number indicating +permission level (from 1 to 4) or a string value, one of: `'all'` (default), `'ops'` (default opped player with permission level of 2), +`'server'` - command accessible only through the server console and commandblocks, but not in chat, `'players'` - opposite +of the former, allowing only use in player chat. It can also be a function (lambda function or function value, not function name) +that takes 1 parameter, which represents the calling player, or `'null'` if the command represents a server call. +The function will prevent the command from running if it evaluates to `false`. +Please note, that Minecraft evaluates eligible commands for players when they join, or on reload/restart, so if you use a +predicate that is volatile and might change, the command might falsely do or do not indicate that it is available to the player, +however player will always be able to type it in and either succeed, or fail, based on their current permissions. +Custom permission applies to legacy commands with `'legacy_command_type_support'` as well +as for the custom commands defined with `'commands'`, see below. * `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well -as for the custom commands defined with `'commands'`, see below -* `'commands'` - defines custom commands for the app to be executed with `/` command, see below +as for the custom commands defined with `'commands'`, see below. +* `'commands'` - defines custom commands for the app to be executed with `/` command, see below. ## Custom app commands diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 12eff864..09e08153 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -11,6 +11,7 @@ import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InvalidCallbackException; +import carpet.script.value.EntityValue; import carpet.script.value.FunctionValue; import carpet.script.value.MapValue; import carpet.script.value.NumericValue; @@ -38,6 +39,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @@ -163,8 +165,9 @@ public class CarpetScriptHost extends ScriptHost ) throws CommandSyntaxException { String hostName = main.getName(); + Function configValidator = getCommandConfigPermissions(); LiteralArgumentBuilder command = literal(hostName). - requires((player) -> CarpetServer.scriptServer.modules.containsKey(hostName) && useValidator.apply(player)); + requires((player) -> CarpetServer.scriptServer.modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)); for (Pair,FunctionArgument> commandData : entries) { command = this.addPathToCommand(command, commandData.getKey(), commandData.getValue()); @@ -172,6 +175,49 @@ public class CarpetScriptHost extends ScriptHost return command; } + public Function getCommandConfigPermissions() throws CommandSyntaxException + { + Value confValue = appConfig.get(StringValue.of("command_permission")); + if (confValue == null) return s -> true; + if (confValue instanceof NumericValue) + { + int level = ((NumericValue) confValue).getInt(); + if (level < 1 || level > 4) throw CommandArgument.error("Numeric permission level for custom commands should be between 1 and 4"); + return s -> s.hasPermissionLevel(level); + } + if (!(confValue instanceof FunctionValue)) + { + String perm = confValue.getString().toLowerCase(Locale.ROOT); + switch (perm) + { + case "ops": return s -> s.hasPermissionLevel(2); + case "server": return s -> !(s.getEntity() instanceof ServerPlayerEntity); + case "players": return s -> s.getEntity() instanceof ServerPlayerEntity; + case "all": return s -> true; + default: throw CommandArgument.error("Unknown command permission: "+perm); + } + } + FunctionValue fun = (FunctionValue) confValue; + if (fun.getNumParams() != 1) throw CommandArgument.error("Custom command permission function should expect 1 argument"); + String hostName = getName(); + return s -> { + try + { + CarpetScriptHost cHost = null; + cHost = CarpetServer.scriptServer.modules.get(hostName).retrieveOwnForExecution(s); + Value response = cHost.handleCommand(s, fun, Collections.singletonList( + (s.getEntity() instanceof ServerPlayerEntity)?new EntityValue(s.getEntity()):Value.NULL) + ); + return response.getBoolean(); + } + catch (CommandSyntaxException e) + { + Messenger.m(s, "rb Unable to run app command: "+e.getMessage()); + return false; + } + }; + } + @Override protected ScriptHost duplicate() { diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 41b2fc34..7f0a3f2c 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -221,7 +221,6 @@ public class CarpetScriptServer public boolean addScriptHost(ServerCommandSource source, String name, Function commandValidator, boolean perPlayer, boolean autoload, boolean isRuleApp) { - //TODO add per player modules to support player actions better on a server if (commandValidator == null) commandValidator = p -> true; long start = System.nanoTime(); name = name.toLowerCase(Locale.ROOT); @@ -325,8 +324,19 @@ public class CarpetScriptServer return true; } + Function configValidator; + try + { + configValidator = host.getCommandConfigPermissions(); + } + catch (CommandSyntaxException e) + { + Messenger.m(source, "rb "+e.getMessage()); + return false; + } + LiteralArgumentBuilder command = literal(hostName). - requires((player) -> modules.containsKey(hostName) && useValidator.apply(player)). + requires((player) -> modules.containsKey(hostName) && useValidator.apply(player) && configValidator.apply(player)). executes( (c) -> { CarpetScriptHost targetHost = modules.get(hostName).retrieveOwnForExecution(c.getSource()); From 15853095920ba4215a365b761469f5a7bd7ca1e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Dec 2020 16:04:19 +0000 Subject: [PATCH 46/64] Merge docs for 'allow apps define their own command permission level, fixes #542' --- docs/scarpet/Full.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 5b25bd4e..14f87168 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1789,10 +1789,9 @@ To access global/server state for a player app, which you shouldn't do, you need so either use a command block, or any arbitrary entity: `/execute as @e[type=bat,limit=1] run script in globals` for instance, however running anything in the global scope for a `'player'` scoped app is not intended. -* `'stay_loaded'`: defaults to `false`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will +* `'stay_loaded'`: defaults to `true`. If true, and `/carpet scriptsAutoload` is turned on, the following apps will stay loaded after startup. Otherwise, after reading the app the first time, and fetching the config, server will drop them down. -This is to allow to store multiple apps on the server/world and selectively decide which one should be running at -startup. WARNING: all apps will run once at startup anyways, so be aware that their actions that are called + WARNING: all apps will run once at startup anyways, so be aware that their actions that are called statically, will be performed once anyways. Only apps present in the world's `scripts` folder will be autoloaded. * `'legacy_command_type_support'` - if `true`, and the app defines the legacy command system via `__command()` function, all parameters of command functions will be interpreted and used using brigadier / vanilla style argument parser and their type @@ -1803,9 +1802,20 @@ conflicts and ambiguities between different paths of execution. While ambiguous and they tend to execute correctly, the suggestion support works really poorly in these situations and scarpet will warn and prevent such apps from loading with an error message. If `allow_command_conflicts` is specified and `true`, then scarpet will load all provided commands regardless. +* `'command_permission'` - indicates a custom permission to run the command. It can either be a number indicating +permission level (from 1 to 4) or a string value, one of: `'all'` (default), `'ops'` (default opped player with permission level of 2), +`'server'` - command accessible only through the server console and commandblocks, but not in chat, `'players'` - opposite +of the former, allowing only use in player chat. It can also be a function (lambda function or function value, not function name) +that takes 1 parameter, which represents the calling player, or `'null'` if the command represents a server call. +The function will prevent the command from running if it evaluates to `false`. +Please note, that Minecraft evaluates eligible commands for players when they join, or on reload/restart, so if you use a +predicate that is volatile and might change, the command might falsely do or do not indicate that it is available to the player, +however player will always be able to type it in and either succeed, or fail, based on their current permissions. +Custom permission applies to legacy commands with `'legacy_command_type_support'` as well +as for the custom commands defined with `'commands'`, see below. * `'arguments'` - defines custom argument types for legacy commands with `'legacy_command_type_support'` as well -as for the custom commands defined with `'commands'`, see below -* `'commands'` - defines custom commands for the app to be executed with `/` command, see below +as for the custom commands defined with `'commands'`, see below. +* `'commands'` - defines custom commands for the app to be executed with `/` command, see below. ## Custom app commands From bf9aa3207a37d4c2a4d511a6eab0f333c972e83d Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 7 Dec 2020 22:30:04 +0100 Subject: [PATCH 47/64] Fix optimizedTNT entity behaviour (#617) Fixes gnembon#616 --- src/main/java/carpet/helpers/OptimizedExplosion.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/helpers/OptimizedExplosion.java b/src/main/java/carpet/helpers/OptimizedExplosion.java index ddaaa92d..3130f30a 100644 --- a/src/main/java/carpet/helpers/OptimizedExplosion.java +++ b/src/main/java/carpet/helpers/OptimizedExplosion.java @@ -132,7 +132,8 @@ public class OptimizedExplosion if (d12 <= 1.0D) { double d5 = entity.getX() - eAccess.getX(); - double d7 = entity.getY() + (double) entity.getStandingEyeHeight() - eAccess.getY(); + // Change in 1.16 snapshots to fix a bug with TNT jumping + double d7 = (entity instanceof TntEntity ? entity.getY() : entity.getEyeY()) - eAccess.getY(); double d9 = entity.getZ() - eAccess.getZ(); double d13 = (double) MathHelper.sqrt(d5 * d5 + d7 * d7 + d9 * d9); From 6dc6a42bda4a8366281e9e2d1e1ee9ab16350196 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 8 Dec 2020 19:15:33 -0400 Subject: [PATCH 48/64] client function, duh. --- src/main/java/carpet/script/api/Auxiliary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index 367fe2d1..eeefd681 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -530,7 +530,7 @@ public class Auxiliary { if (pVal instanceof FormattedTextValue) title = ((FormattedTextValue) pVal).getText(); else - title = Text.of(pVal.getString()); + title = new LiteralText(pVal.getString()); } TitleS2CPacket timesPacket; if (lv.size() > 3) From 97971322e578907413720345cbf776bab55f0a16 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 11 Dec 2020 00:14:11 -0400 Subject: [PATCH 49/64] better death messages for fake players and handling idling. Fixes #619 --- .../carpet/mixins/PlayerManager_fakePlayersMixin.java | 2 +- src/main/java/carpet/patches/EntityPlayerMPFake.java | 10 ++++++++-- .../java/carpet/patches/NetHandlerPlayServerFake.java | 5 +++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/carpet/mixins/PlayerManager_fakePlayersMixin.java b/src/main/java/carpet/mixins/PlayerManager_fakePlayersMixin.java index faee9ef4..1b5f6be8 100644 --- a/src/main/java/carpet/mixins/PlayerManager_fakePlayersMixin.java +++ b/src/main/java/carpet/mixins/PlayerManager_fakePlayersMixin.java @@ -69,7 +69,7 @@ public abstract class PlayerManager_fakePlayersMixin ServerPlayerEntity serverPlayerEntity_3 = (ServerPlayerEntity) var5.next(); if(serverPlayerEntity_3 instanceof EntityPlayerMPFake) { - serverPlayerEntity_3.kill(); + ((EntityPlayerMPFake)serverPlayerEntity_3).kill(new TranslatableText("multiplayer.disconnect.duplicate_login")); continue; } serverPlayerEntity_3.networkHandler.disconnect(new TranslatableText("multiplayer.disconnect.duplicate_login")); diff --git a/src/main/java/carpet/patches/EntityPlayerMPFake.java b/src/main/java/carpet/patches/EntityPlayerMPFake.java index 9befa9db..e7932250 100644 --- a/src/main/java/carpet/patches/EntityPlayerMPFake.java +++ b/src/main/java/carpet/patches/EntityPlayerMPFake.java @@ -9,6 +9,7 @@ import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.HungerManager; import net.minecraft.network.NetworkSide; +import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerTask; @@ -95,9 +96,14 @@ public class EntityPlayerMPFake extends ServerPlayerEntity @Override public void kill() + { + kill(Messenger.s("Killed")); + } + + public void kill(Text reason) { this.server.send(new ServerTask(this.server.getTicks(), () -> { - this.networkHandler.onDisconnected(Messenger.s("Killed")); + this.networkHandler.onDisconnected(reason); })); } @@ -120,7 +126,7 @@ public class EntityPlayerMPFake extends ServerPlayerEntity super.onDeath(cause); setHealth(20); this.hungerManager = new HungerManager(); - kill(); + kill(this.getDamageTracker().getDeathMessage()); } @Override diff --git a/src/main/java/carpet/patches/NetHandlerPlayServerFake.java b/src/main/java/carpet/patches/NetHandlerPlayServerFake.java index af85ff30..330cb54d 100644 --- a/src/main/java/carpet/patches/NetHandlerPlayServerFake.java +++ b/src/main/java/carpet/patches/NetHandlerPlayServerFake.java @@ -6,6 +6,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler { @@ -22,6 +23,10 @@ public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler @Override public void disconnect(Text message) { + if (player instanceof EntityPlayerMPFake && message instanceof TranslatableText && ((TranslatableText) message).getKey().equals("multiplayer.disconnect.idling")) + { + ((EntityPlayerMPFake) player).kill(new TranslatableText(((TranslatableText) message).getKey())); + } } } From 2be4ac8ee024acdd2fca11de8221000dedc110d3 Mon Sep 17 00:00:00 2001 From: Kevin Delugan Date: Fri, 11 Dec 2020 05:19:06 +0100 Subject: [PATCH 50/64] Added onTrade Scarpet Event --- docs/scarpet/api/Events.md | 7 +++- .../TradeOutputSlot_scarpetEventMixin.java | 33 +++++++++++++++++++ .../java/carpet/script/CarpetEventServer.java | 18 ++++++++++ .../assets/carpet/scripts/event_test.sc | 11 +++++++ src/main/resources/carpet.mixins.json | 1 + 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index 18e3f2ba..d2f6e4b2 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -114,7 +114,12 @@ adjusted. ### `__on_player_interacts_with_entity(player, entity, hand)` Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side -with that interaction +with that interaction. + +### `__on_player_trades(player, entity, buy, buyB, sell)` +Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory +changes and merchant updates its trade-uses counter. +The parameter `entity` can be `null` if the merchant is not an entity. ### `__on_player_collides_with_entity(player, entity)` Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game. diff --git a/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java new file mode 100644 index 00000000..7cf0ff2b --- /dev/null +++ b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java @@ -0,0 +1,33 @@ +package carpet.mixins; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.TradeOutputSlot; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.village.Merchant; +import net.minecraft.village.TradeOffer; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import static carpet.script.CarpetEventServer.Event.PLAYER_TRADES; + +@Mixin(value = TradeOutputSlot.class) +public abstract class TradeOutputSlot_scarpetEventMixin { + @Shadow @Final private Merchant merchant; + + @Inject(method = "onTakeItem", require = 0, at = @At(value = "INVOKE", + target = "Lnet/minecraft/village/Merchant;trade(Lnet/minecraft/village/TradeOffer;)V"), + locals = LocalCapture.CAPTURE_FAILHARD + ) + private void onTrade(PlayerEntity player, ItemStack stack, CallbackInfoReturnable cir, TradeOffer tradeOffer) { + if(PLAYER_TRADES.isNeeded() && !this.merchant.getMerchantWorld().isClient()) + { + PLAYER_TRADES.onTrade((ServerPlayerEntity) player, merchant, tradeOffer); + } + } +} diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 11e89c03..4b6b31b6 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -18,6 +18,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.block.BlockState; import net.minecraft.entity.EntityType; import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.passive.MerchantEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.entity.Entity; @@ -35,6 +36,8 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.RegistryKey; +import net.minecraft.village.Merchant; +import net.minecraft.village.TradeOffer; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import org.apache.commons.lang3.tuple.Pair; @@ -548,6 +551,20 @@ public class CarpetEventServer ), player::getCommandSource); } }; + public static final Event PLAYER_TRADES = new Event("player_trades", 5, false) + { + @Override + public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) + { + handler.call( () -> Arrays.asList( + new EntityValue(player), + merchant instanceof MerchantEntity ? new EntityValue((MerchantEntity) merchant) : Value.NULL, + ValueConversions.of(tradeOffer.getOriginalFirstBuyItem()), + ValueConversions.of(tradeOffer.getSecondBuyItem()), + ValueConversions.of(tradeOffer.getSellItem()) + ), player::getCommandSource); + } + }; public static final Event PLAYER_PICKS_UP_ITEM = new Event("player_picks_up_item", 2, false) { @Override @@ -926,6 +943,7 @@ public class CarpetEventServer public void onDamage(Entity target, float amount, DamageSource source) { } public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {} public void onSlotSwitch(ServerPlayerEntity player, int from, int to) {} + public void onTrade(ServerPlayerEntity player, Merchant merchant, TradeOffer tradeOffer) {} public void onWorldEvent(ServerWorld world, BlockPos pos) { } diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index 3c6c989e..91fccdac 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -292,3 +292,14 @@ __on_player_picks_up_item(player, item) -> print(' Player '+player+' injects '+item); ); +__on_player_trades(player, entity, buy, buyB, sell) -> +( + print(''); + print('__on_player_trades(player, item)'); + print('player trades:'); + print(' - player: '+player); + print(' - entity: '+ entity); + print(' - buy: '+ buy); + print(' - buyB: '+ buyB); + print(' - sell: '+ sell); +); diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 024e8f8e..1551b661 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -164,6 +164,7 @@ "BlockItem_scarpetEventMixin", "PlayerManager_scarpetEventsMixin", "PlayerInventory_scarpetEventMixin", + "TradeOutputSlot_scarpetEventMixin", "ChunkTicketManager_scarpetMixin", "StatType_scarpetMixin", From 5e98fbef6618c1f3ac29c6bd87b76aaf1942dcb2 Mon Sep 17 00:00:00 2001 From: gnembon <41132274+gnembon@users.noreply.github.com> Date: Sat, 12 Dec 2020 00:42:03 -0400 Subject: [PATCH 51/64] Update src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java --- .../java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java index 7cf0ff2b..8c6e57c1 100644 --- a/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java @@ -20,7 +20,7 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_TRADES; public abstract class TradeOutputSlot_scarpetEventMixin { @Shadow @Final private Merchant merchant; - @Inject(method = "onTakeItem", require = 0, at = @At(value = "INVOKE", + @Inject(method = "onTakeItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/village/Merchant;trade(Lnet/minecraft/village/TradeOffer;)V"), locals = LocalCapture.CAPTURE_FAILHARD ) From 0e41ce44273991ef67aeb16601fead1ae455da24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 12 Dec 2020 04:43:35 +0000 Subject: [PATCH 52/64] Merge docs for 'Merge pull request #623 from BisUmTo/onTrade-Scarpet-Event Added onTrade Scarpet Event' --- docs/scarpet/Full.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 14f87168..3485a4bb 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -4051,7 +4051,12 @@ adjusted. ### `__on_player_interacts_with_entity(player, entity, hand)` Triggered when player right clicks (interacts) with an entity, even if the entity has no vanilla interaction with the player or the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side -with that interaction +with that interaction. + +### `__on_player_trades(player, entity, buy, buyB, sell)` +Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory +changes and merchant updates its trade-uses counter. +The parameter `entity` can be `null` if the merchant is not an entity. ### `__on_player_collides_with_entity(player, entity)` Triggered every time a player - entity collisions are calculated, before effects of collisions are applied in the game. From 34ee6784a45962ce4b34b5afee22f0fdaa3ad8b0 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 13 Dec 2020 06:07:13 -0400 Subject: [PATCH 53/64] values close to zero don't round nicely with bigdecs --- src/main/java/carpet/script/value/NumericValue.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java index caec9776..26b6ec56 100644 --- a/src/main/java/carpet/script/value/NumericValue.java +++ b/src/main/java/carpet/script/value/NumericValue.java @@ -15,6 +15,7 @@ import java.math.RoundingMode; import java.util.Locale; import static java.lang.Math.abs; +import static java.lang.Math.signum; public class NumericValue extends Value { @@ -57,6 +58,7 @@ public class NumericValue extends Value { if (value.isInfinite()) return "INFINITY"; if (value.isNaN()) return "NaN"; + if (abs(value) < epsilon) return (signum(value) < 0)?"-0":"0"; //zero rounding fails with big decimals // dobules have 16 point precision, 12 is plenty to display return BigDecimal.valueOf(value).round(displayRounding).stripTrailingZeros().toPlainString(); } From 7bacea181071a7e0af10f49ebfd5b419fa88c420 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 13 Dec 2020 06:29:42 -0400 Subject: [PATCH 54/64] range now handles better long long chains, as well as float values. --- docs/scarpet/language/Containers.md | 2 + .../script/language/DataStructures.java | 16 +++--- .../carpet/script/value/LazyListValue.java | 55 +++++++++++++++++-- .../carpet/script/value/NumericValue.java | 5 ++ src/main/java/carpet/script/value/Value.java | 10 ++-- 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/docs/scarpet/language/Containers.md b/docs/scarpet/language/Containers.md index 1f4759d9..8b07497c 100644 --- a/docs/scarpet/language/Containers.md +++ b/docs/scarpet/language/Containers.md @@ -215,6 +215,8 @@ map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] reduce(range(10),_a+_, 0) => 45 range(5,10) => [5, 6, 7, 8, 9] range(20, 10, -2) => [20, 18, 16, 14, 12] +range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2] +range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2] ## Map operations diff --git a/src/main/java/carpet/script/language/DataStructures.java b/src/main/java/carpet/script/language/DataStructures.java index c61f5d42..10c21c94 100644 --- a/src/main/java/carpet/script/language/DataStructures.java +++ b/src/main/java/carpet/script/language/DataStructures.java @@ -135,23 +135,25 @@ public class DataStructures { expression.addFunction("range", (lv) -> { - long from = 0; - long to = 0; - long step = 1; + NumericValue from = Value.ZERO; + NumericValue to; + NumericValue step = Value.ONE; int argsize = lv.size(); if (argsize == 0 || argsize > 3) throw new InternalExpressionException("'range' accepts from 1 to 3 arguments, not "+argsize); - to = NumericValue.asNumber(lv.get(0)).getLong(); + to = NumericValue.asNumber(lv.get(0)); if (lv.size() > 1) { from = to; - to = NumericValue.asNumber(lv.get(1)).getLong(); + to = NumericValue.asNumber(lv.get(1)); if (lv.size() > 2) { - step = NumericValue.asNumber(lv.get(2)).getLong(); + step = NumericValue.asNumber(lv.get(2)); } } - return LazyListValue.range(from, to, step); + return (from.isInteger() && to.isInteger() && step.isInteger()) + ? LazyListValue.rangeLong(from.getLong(), to.getLong(), step.getLong()) + : LazyListValue.rangeDouble(from.getDouble(), to.getDouble(), step.getDouble()); }); expression.addLazyFunction("m", -1, (c, t, llv) -> diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index 2edd637d..5c68a75a 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -11,7 +11,7 @@ import java.util.Locale; public abstract class LazyListValue extends AbstractListValue implements Iterator { - public static LazyListValue range(long from, long to, long step) + public static LazyListValue rangeDouble(double from, double to, double step) { return new LazyListValue() { @@ -23,10 +23,10 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato this.limit = to; this.stepp = step; } - private long start; - private long current; - private long limit; - private long stepp; + private final double start; + private double current; + private final double limit; + private final double stepp; @Override public Value next() { @@ -50,7 +50,50 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato @Override public String getString() { - return String.format(Locale.ROOT, "[%d, %d, ..., %d)",start, start+stepp, limit); + return String.format(Locale.ROOT, "[%s, %s, ..., %s)",NumericValue.of(start).getString(), NumericValue.of(start+stepp).getString(), NumericValue.of( limit).getString()); + } + }; + } + public static LazyListValue rangeLong(long from, long to, long step) + { + return new LazyListValue() + { + { + if (step == 0) + throw new InternalExpressionException("Range will never end with a zero step"); + this.start = from; + this.current = this.start; + this.limit = to; + this.stepp = step; + } + private final long start; + private long current; + private final long limit; + private final long stepp; + @Override + public Value next() + { + Value val = new NumericValue(current); + current += stepp; + return val; + } + + @Override + public void reset() + { + current = start; + } + + @Override + public boolean hasNext() + { + return stepp > 0?(current < limit):(current > limit); + } + + @Override + public String getString() + { + return String.format(Locale.ROOT, "[%s, %s, ..., %s)",NumericValue.of(start).getString(), NumericValue.of(start+stepp).getString(), NumericValue.of( limit).getString()); } }; } diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java index 26b6ec56..00a506f4 100644 --- a/src/main/java/carpet/script/value/NumericValue.java +++ b/src/main/java/carpet/script/value/NumericValue.java @@ -308,4 +308,9 @@ public class NumericValue extends Value if (longValue != null) return new NumericValue(-longValue); return new NumericValue(-value); } + + public boolean isInteger() + { + return longValue!= null || getDouble() == (double)getLong(); + } } diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 91e3e893..679a9263 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -13,10 +13,12 @@ import java.util.regex.PatternSyntaxException; public abstract class Value implements Comparable, Cloneable { - public static Value FALSE = new NumericValue(0); - public static Value TRUE = new NumericValue(1); - public static Value ZERO = FALSE; - public static Value NULL = new NullValue(); + public static NumericValue FALSE = new NumericValue(0); + public static NumericValue TRUE = new NumericValue(1); + public static NumericValue ZERO = FALSE; + public static NumericValue ONE = TRUE; + + public static NullValue NULL = new NullValue(); public String boundVariable; From 6f28425eaffad233b061e12bf1fe53d9fcf2e579 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 13 Dec 2020 06:30:40 +0000 Subject: [PATCH 55/64] Merge docs for 'Merge branch 'master' of https://github.com/gnembon/fabric-carpet' --- docs/scarpet/Full.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 3485a4bb..5753b3c2 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -1710,6 +1710,8 @@ map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] reduce(range(10),_a+_, 0) => 45 range(5,10) => [5, 6, 7, 8, 9] range(20, 10, -2) => [20, 18, 16, 14, 12] +range(-0.3, 0.3, 0.1) => [-0.3, -0.2, -0.1, 0, 0.1, 0.2] +range(0.3, -0.3, -0.1) => [0.3, 0.2, 0.1, -0, -0.1, -0.2] ## Map operations From cd892f95bc25449a9c9e0a9150f9196fbdb3a726 Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 14 Dec 2020 02:36:53 -0400 Subject: [PATCH 56/64] cleaner --- docs/scarpet/api/Events.md | 2 +- .../mixins/TradeOutputSlot_scarpetEventMixin.java | 11 ++++++----- .../resources/assets/carpet/scripts/event_test.sc | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/scarpet/api/Events.md b/docs/scarpet/api/Events.md index d2f6e4b2..8ff9a8b7 100644 --- a/docs/scarpet/api/Events.md +++ b/docs/scarpet/api/Events.md @@ -116,7 +116,7 @@ Triggered when player right clicks (interacts) with an entity, even if the entit the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side with that interaction. -### `__on_player_trades(player, entity, buy, buyB, sell)` +### `__on_player_trades(player, entity, buy_left, buy_right, sell)` Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory changes and merchant updates its trade-uses counter. The parameter `entity` can be `null` if the merchant is not an entity. diff --git a/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java index 8c6e57c1..053e799b 100644 --- a/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/TradeOutputSlot_scarpetEventMixin.java @@ -5,7 +5,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.screen.slot.TradeOutputSlot; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.village.Merchant; -import net.minecraft.village.TradeOffer; +import net.minecraft.village.MerchantInventory; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,14 +20,15 @@ import static carpet.script.CarpetEventServer.Event.PLAYER_TRADES; public abstract class TradeOutputSlot_scarpetEventMixin { @Shadow @Final private Merchant merchant; + @Shadow @Final private MerchantInventory merchantInventory; + @Inject(method = "onTakeItem", at = @At(value = "INVOKE", - target = "Lnet/minecraft/village/Merchant;trade(Lnet/minecraft/village/TradeOffer;)V"), - locals = LocalCapture.CAPTURE_FAILHARD + target = "Lnet/minecraft/village/Merchant;trade(Lnet/minecraft/village/TradeOffer;)V") ) - private void onTrade(PlayerEntity player, ItemStack stack, CallbackInfoReturnable cir, TradeOffer tradeOffer) { + private void onTrade(PlayerEntity player, ItemStack stack, CallbackInfoReturnable cir) { if(PLAYER_TRADES.isNeeded() && !this.merchant.getMerchantWorld().isClient()) { - PLAYER_TRADES.onTrade((ServerPlayerEntity) player, merchant, tradeOffer); + PLAYER_TRADES.onTrade((ServerPlayerEntity) player, merchant, merchantInventory.getTradeOffer()); } } } diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index 91fccdac..11842f56 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -292,14 +292,14 @@ __on_player_picks_up_item(player, item) -> print(' Player '+player+' injects '+item); ); -__on_player_trades(player, entity, buy, buyB, sell) -> +__on_player_trades(player, entity, buy_left, buy_right, sell) -> ( print(''); print('__on_player_trades(player, item)'); print('player trades:'); print(' - player: '+player); print(' - entity: '+ entity); - print(' - buy: '+ buy); - print(' - buyB: '+ buyB); - print(' - sell: '+ sell); + print(' - buy_left: '+ buy_left); + print(' - buy_right: '+ buy_right); + print(' - sell: '+ sell) ); From f859eb87b5fcf317dc909a73d7f7db1e8b014620 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 14 Dec 2020 06:37:29 +0000 Subject: [PATCH 57/64] Merge docs for 'cleaner' --- docs/scarpet/Full.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 5753b3c2..248dbd24 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -4055,7 +4055,7 @@ Triggered when player right clicks (interacts) with an entity, even if the entit the item they are holding. The event is invoked after receiving a packet from the client, before anything happens server side with that interaction. -### `__on_player_trades(player, entity, buy, buyB, sell)` +### `__on_player_trades(player, entity, buy_left, buy_right, sell)` Triggered when player trades with a merchant. The event is invoked after the server allow the trade, but before the inventory changes and merchant updates its trade-uses counter. The parameter `entity` can be `null` if the merchant is not an entity. From 7b77fb08531ca3858ecfeaa296d6e983a0e943ec Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 14 Dec 2020 18:36:24 +0100 Subject: [PATCH 58/64] Add FormattedText addition --- .../carpet/script/value/FormattedTextValue.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index 218575ca..529c340a 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -48,10 +48,25 @@ public class FormattedTextValue extends StringValue return StringTag.of(Text.Serializer.toJson(text)); } + @Override + public Value add(Value o) { + if (o instanceof FormattedTextValue) + { + Text mergedText = ((BaseText) text).shallowCopy().append(((FormattedTextValue) o).getText()); + return new FormattedTextValue(mergedText); + } + else + { + Text mergedText = ((BaseText) text).shallowCopy().append(o.getString()); + return new FormattedTextValue(mergedText); + } + } + public String serialize() { return Text.Serializer.toJson(text); } + public static FormattedTextValue deserialize(String serialized) { return new FormattedTextValue(Text.Serializer.fromJson(serialized)); From 6dfd64027d78dd9affd9502ac1e217ed203ab936 Mon Sep 17 00:00:00 2001 From: altrisi Date: Mon, 14 Dec 2020 19:20:21 +0100 Subject: [PATCH 59/64] Add FormattedText functionality to the right of the operand --- .../java/carpet/script/value/FormattedTextValue.java | 2 +- src/main/java/carpet/script/value/Value.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index 529c340a..70546d71 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -27,7 +27,7 @@ public class FormattedTextValue extends StringValue @Override public Value clone() { - return new FormattedTextValue(text); + return new FormattedTextValue(text.shallowCopy()); } @Override diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 679a9263..93e32183 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -4,6 +4,8 @@ import carpet.script.exception.InternalExpressionException; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import net.minecraft.nbt.Tag; +import net.minecraft.text.BaseText; +import net.minecraft.text.LiteralText; import java.util.ArrayList; import java.util.List; @@ -79,6 +81,14 @@ public abstract class Value implements Comparable, Cloneable public Value add(Value o) { String lstr = this.getString(); + if (o instanceof FormattedTextValue) + { + if (lstr == null) + return ((FormattedTextValue) o).clone(); + BaseText newText = new LiteralText(lstr); + newText.append(((FormattedTextValue) o).getText().shallowCopy()); + return new FormattedTextValue(newText); + } if (lstr == null) // null return new StringValue(o.getString()); String rstr = o.getString(); From a81b549360bbab7fb23a47dcf252e081d19328e1 Mon Sep 17 00:00:00 2001 From: altrisi Date: Tue, 15 Dec 2020 10:00:09 +0100 Subject: [PATCH 60/64] Move FormattedText addition into a combined function --- .../script/value/FormattedTextValue.java | 41 ++++++++++++++----- src/main/java/carpet/script/value/Value.java | 8 +--- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/main/java/carpet/script/value/FormattedTextValue.java b/src/main/java/carpet/script/value/FormattedTextValue.java index 70546d71..25e45bf0 100644 --- a/src/main/java/carpet/script/value/FormattedTextValue.java +++ b/src/main/java/carpet/script/value/FormattedTextValue.java @@ -3,6 +3,7 @@ package carpet.script.value; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.text.BaseText; +import net.minecraft.text.LiteralText; import net.minecraft.text.Text; public class FormattedTextValue extends StringValue @@ -14,6 +15,33 @@ public class FormattedTextValue extends StringValue this.text = text; } + public static Value combine(Value left, Value right) { + BaseText text; + if (left instanceof FormattedTextValue) + { + text = (BaseText) ((FormattedTextValue) left).getText().shallowCopy(); + } + else + { + if (left instanceof NullValue) + return right; + text = new LiteralText(left.getString()); + } + + if (right instanceof FormattedTextValue) + { + text.append(((FormattedTextValue) right).getText().shallowCopy()); + return new FormattedTextValue(text); + } + else + { + if (right instanceof NullValue) + return left; + text.append(right.getString()); + return new FormattedTextValue(text); + } + } + @Override public String getString() { return text.getString(); @@ -27,7 +55,7 @@ public class FormattedTextValue extends StringValue @Override public Value clone() { - return new FormattedTextValue(text.shallowCopy()); + return new FormattedTextValue(text); } @Override @@ -50,16 +78,7 @@ public class FormattedTextValue extends StringValue @Override public Value add(Value o) { - if (o instanceof FormattedTextValue) - { - Text mergedText = ((BaseText) text).shallowCopy().append(((FormattedTextValue) o).getText()); - return new FormattedTextValue(mergedText); - } - else - { - Text mergedText = ((BaseText) text).shallowCopy().append(o.getString()); - return new FormattedTextValue(mergedText); - } + return combine(this, o); } public String serialize() diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 93e32183..c2f188e7 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -4,8 +4,6 @@ import carpet.script.exception.InternalExpressionException; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; import net.minecraft.nbt.Tag; -import net.minecraft.text.BaseText; -import net.minecraft.text.LiteralText; import java.util.ArrayList; import java.util.List; @@ -83,11 +81,7 @@ public abstract class Value implements Comparable, Cloneable String lstr = this.getString(); if (o instanceof FormattedTextValue) { - if (lstr == null) - return ((FormattedTextValue) o).clone(); - BaseText newText = new LiteralText(lstr); - newText.append(((FormattedTextValue) o).getText().shallowCopy()); - return new FormattedTextValue(newText); + return FormattedTextValue.combine(this, o); } if (lstr == null) // null return new StringValue(o.getString()); From 4466f9e8bb9652c14df921c2a37085b113f8ad24 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 16 Dec 2020 01:37:00 -0400 Subject: [PATCH 61/64] set vanishes block data on unsuccessful replace. Fixes #629 --- src/main/java/carpet/script/api/WorldAccess.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index a9f8e780..82221c84 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -812,7 +812,7 @@ public class WorldAccess { { Clearable.clear(world.getBlockEntity(targetPos)); boolean success = world.setBlockState(targetPos, finalSourceBlockState, 2); - if (success && finalData != null) + if (finalData != null) { BlockEntity be = world.getBlockEntity(targetPos); if (be != null) @@ -823,6 +823,7 @@ public class WorldAccess { destTag.putInt("z", targetPos.getZ()); be.fromTag(finalSourceBlockState, destTag); be.markDirty(); + success = true; } } result[0] = success; From 5280cda3ee1ec89ce52d3e9af29fe5f1e71d3f2e Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 16 Dec 2020 02:05:05 -0400 Subject: [PATCH 62/64] set can now accept map of properties. --- docs/scarpet/api/BlocksAndWorldAccess.md | 25 +++++++++++-------- .../java/carpet/script/api/WorldAccess.java | 15 +++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/scarpet/api/BlocksAndWorldAccess.md b/docs/scarpet/api/BlocksAndWorldAccess.md index 990ae027..a7419d0f 100644 --- a/docs/scarpet/api/BlocksAndWorldAccess.md +++ b/docs/scarpet/api/BlocksAndWorldAccess.md @@ -36,18 +36,19 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block` All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the functions referenced below refer to either method of passing block position. -### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)` -First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. -Second part, `block` is either block value as a result of `block()` function string value indicating the block name, -and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to +First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value. +Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name +with optional state and block data. It is then followed by an optional +`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to be set for the target block. If `block` is specified only by name, then if a destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra -properties. +properties that the original source block may have contained. -The returned value is either the block state that has been set, or `false` if block setting was skipped +The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
 set(0,5,0,'bedrock')  => bedrock
@@ -63,11 +64,15 @@ scan(0,100,0,20,20,20,set(_,block('glass')))
 b = block('glass'); scan(0,100,0,20,20,20,set(_,b))
     // yet another option, skips all parsing
 set(x,y,z,'iron_trapdoor')  // sets bottom iron trapdoor
-set(x,y,z,'iron_trapdoor[half=top]')  // Incorrect. sets bottom iron trapdoor - no parsing of properties
-set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor
-set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
+
+set(x,y,z,'iron_trapdoor[half=top]')  // sets the top trapdoor
+set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor
+set(x,y,z,'iron_trapdoor', ['half','top']) // same
+set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same
+set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state
+
 set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data
-set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
+set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
 
### `without_updates(expr)` diff --git a/src/main/java/carpet/script/api/WorldAccess.java b/src/main/java/carpet/script/api/WorldAccess.java index 82221c84..d15da361 100644 --- a/src/main/java/carpet/script/api/WorldAccess.java +++ b/src/main/java/carpet/script/api/WorldAccess.java @@ -777,6 +777,21 @@ public class WorldAccess { } args = ((ListValue) args.get(0)).getItems(); } + else if (args.get(0) instanceof MapValue) + { + if (args.size() == 2) + { + Value dataValue = NBTSerializableValue.fromValue( args.get(1)); + if (dataValue instanceof NBTSerializableValue) + { + data = ((NBTSerializableValue) dataValue).getCompoundTag(); + } + } + Map state = ((MapValue) args.get(0)).getMap(); + List mapargs = new ArrayList<>(); + state.forEach( (k, v) -> {mapargs.add(k); mapargs.add(v);}); + args = mapargs; + } else { if ((args.size() & 1) == 1) From 67c41025b115b52cd3976e0a90d069ef4fdfa4c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 16 Dec 2020 06:05:32 +0000 Subject: [PATCH 63/64] Merge docs for 'set can now accept map of properties.' --- docs/scarpet/Full.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/scarpet/Full.md b/docs/scarpet/Full.md index 248dbd24..f1823eb3 100644 --- a/docs/scarpet/Full.md +++ b/docs/scarpet/Full.md @@ -2072,18 +2072,19 @@ print(b); // 'stone', block was evaluated 'eagerly' but call to `block` All the functions below can be used with block value, queried with coord triple, or 3-long list. All `pos` in the functions referenced below refer to either method of passing block position. -### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, l(property?, value?, ...), block_data?)` +### `set(pos, block, property?, value?, ..., block_data?)`, `set(pos, block, [property?, value?, ...], block_data?)`, `set(pos, block, {property? -> value?, ...}, block_data?)` -First part of the `set` function is either a coord triple, list of three numbers, or other block with coordinates. -Second part, `block` is either block value as a result of `block()` function string value indicating the block name, -and optional `property - value` pairs for extra block properties. Optional `block_data` include the block data to +First argument for the `set` function is either a coord triple, list of three numbers, or a world localized block value. +Second argument, `block`, is either an existing block value, a result of `block()` function, or a string value indicating the block name +with optional state and block data. It is then followed by an optional +`property - value` pairs for extra block state (which can also be provided in a list or a map). Optional `block_data` include the block data to be set for the target block. If `block` is specified only by name, then if a destination block is the same the `set` operation is skipped, otherwise is executed, for other potential extra -properties. +properties that the original source block may have contained. -The returned value is either the block state that has been set, or `false` if block setting was skipped +The returned value is either the block state that has been set, or `false` if block setting was skipped, or failed
 set(0,5,0,'bedrock')  => bedrock
@@ -2099,11 +2100,15 @@ scan(0,100,0,20,20,20,set(_,block('glass')))
 b = block('glass'); scan(0,100,0,20,20,20,set(_,b))
     // yet another option, skips all parsing
 set(x,y,z,'iron_trapdoor')  // sets bottom iron trapdoor
-set(x,y,z,'iron_trapdoor[half=top]')  // Incorrect. sets bottom iron trapdoor - no parsing of properties
-set(x,y,z,'iron_trapdoor','half','top') // correct - top trapdoor
-set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing
+
+set(x,y,z,'iron_trapdoor[half=top]')  // sets the top trapdoor
+set(x,y,z,'iron_trapdoor','half','top') // also correct - top trapdoor
+set(x,y,z,'iron_trapdoor', ['half','top']) // same
+set(x,y,z,'iron_trapdoor', {'half' -> 'top'}) // same
+set(x,y,z, block('iron_trapdoor[half=top]')) // also correct, block() provides extra parsing of block state
+
 set(x,y,z,'hopper[facing=north]{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') // extra block data
-set(x,y,z,'hopper', l('facing', 'north'), nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
+set(x,y,z,'hopper', {'facing' -> 'north'}, nbt('{Items:[{Slot:1b,id:"minecraft:slime_ball",Count:16b}]}') ) // same
 
### `without_updates(expr)` From c6529c3d3c0366dc6a50677f4acc61a71a558e00 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 16 Dec 2020 18:03:04 -0400 Subject: [PATCH 64/64] 1.4.21 --- gradle.properties | 2 +- src/main/java/carpet/CarpetSettings.java | 2 +- src/main/resources/fabric.mod.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6080a184..9551a36d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.10.8 # Mod Properties - mod_version = 1.4.20+v201202 + mod_version = 1.4.21+v201216 maven_group = carpet archives_base_name = fabric-carpet diff --git a/src/main/java/carpet/CarpetSettings.java b/src/main/java/carpet/CarpetSettings.java index 78257a10..172a3cb8 100644 --- a/src/main/java/carpet/CarpetSettings.java +++ b/src/main/java/carpet/CarpetSettings.java @@ -36,7 +36,7 @@ import static carpet.settings.RuleCategory.CLIENT; @SuppressWarnings("CanBeFinal") public class CarpetSettings { - public static final String carpetVersion = "1.4.20+v201202"; + public static final String carpetVersion = "1.4.21+v201216"; public static final Logger LOG = LogManager.getLogger("carpet"); public static boolean skipGenerationChecks = false; public static boolean impendingFillSkipUpdates = false; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index c8144ace..be2c43ff 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "carpet", - "version": "1.4.20", + "version": "1.4.21", "name": "Carpet Mod in Fabric", "description": "Carpet made out of fabric",